From: Jimmy Huang
Date: Fri, 31 Aug 2012 22:21:38 +0000 (-0700)
Subject: Initial import to Tizen
X-Git-Tag: 1.0_branch^0
X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F09%2F1609%2F1;p=profile%2Fivi%2Fpython-twisted.git
Initial import to Tizen
Signed-off-by: Jimmy Huang
---
30d855cbca8385abefa3ffe95811a2bfaa666cf8
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..52071ed
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,33 @@
+Requirements
+
+ Python 2.5, 2.6 or 2.7.
+
+ Zope Interfaces 3.3.0 or better (http://pypi.python.org/pypi/zope.interface)
+
+ pyOpenSSL () is required for any SSL APIs. On
+ Windows, version 0.10 or newer is required. pyOpenSSL 0.10 or newer is also
+ preferred on other platforms, but older versions will work as well.
+
+ On Windows pywin32 ( ) is
+ required. Build 215 or later is highly recommended for reliable operation
+ (this is already included in ActivePython).
+
+ If you would like to use Trial's subunit reporter, then you will need to
+ install Subunit 0.0.2 or later (https://launchpad.net/subunit).
+
+Installation
+
+ * Debian and Ubuntu
+ Packages are included in the main distribution.
+
+ * FreeBSD, Gentoo
+ Twisted is in their package repositories.
+
+ * Win32
+ Installers are available from http://twistedmatrix.com/
+
+ * Other
+ As with other Python packages, the standard way of installing from source
+ is:
+
+ python setup.py install
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..159debb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,57 @@
+Copyright (c) 2001-2012
+Allen Short
+Andy Gayton
+Andrew Bennetts
+Antoine Pitrou
+Apple Computer, Inc.
+Benjamin Bruheim
+Bob Ippolito
+Canonical Limited
+Christopher Armstrong
+David Reid
+Donovan Preston
+Eric Mangold
+Eyal Lotem
+Itamar Turner-Trauring
+James Knight
+Jason A. Mobarak
+Jean-Paul Calderone
+Jessica McKellar
+Jonathan Jacobs
+Jonathan Lange
+Jonathan D. Simms
+Jürgen Hermann
+Kevin Horn
+Kevin Turner
+Mary Gardiner
+Matthew Lefkowitz
+Massachusetts Institute of Technology
+Moshe Zadka
+Paul Swartz
+Pavel Pergamenshchik
+Ralph Meijer
+Sean Riley
+Software Freedom Conservancy
+Travis B. Hartwell
+Thijs Triemstra
+Thomas Herve
+Timothy Allen
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..0f5f154
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,3168 @@
+Ticket numbers in this file can be looked up by visiting
+http://twistedmatrix.com/trac/ticket/
+
+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 and . (#5306)
+
+Bugfixes
+--------
+ - twisted.web.client.getPage and .downloadPage now only fire their
+ result Deferred after the underlying connection they use has been
+ closed. (#3796)
+ - twisted.web.server now omits the default Content-Type header from
+ NOT MODIFIED responses. (#4156)
+ - twisted.web.server now responds correctly to 'Expect: 100-continue'
+ headers, although this is not yet usefully exposed to user code.
+ (#4673)
+ - twisted.web.client.Agent no longer raises an exception if a server
+ responds and closes the connection before the request has been
+ fully transmitted. (#5013)
+ - twisted.web.http_headers.Headers now correctly capitalizes the
+ header names Content-MD5, DNT, ETag, P3P, TE, and X-XSS-Protection.
+ (#5054)
+ - twisted.web.template now escapes more inputs to comments which
+ require escaping in the output. (#5275)
+
+Improved Documentation
+----------------------
+ - The twisted.web.template howto now documents the common idiom of
+ yielding tag clones from a renderer. (#5286)
+ - CookieAgent is now documented in the twisted.web.client how-to.
+ (#5110)
+
+Deprecations and Removals
+-------------------------
+ - twisted.web.google is now deprecated. (#5209)
+
+Other
+-----
+ - #4951, #5057, #5175, #5288, #5316
+
+
+Twisted Words 11.1.0 (2011-11-15)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient now uses a PING heartbeat as
+ a keepalive to avoid losing an IRC connection without being aware
+ of it. (#5047)
+
+Bugfixes
+--------
+ - twisted.words.protocols.irc.IRCClient now replies only once to
+ known CTCP queries per message and not at all to unknown CTCP
+ queries. (#5029)
+ - IRCClient.msg now determines a safe maximum command length,
+ drastically reducing the chance of relayed text being truncated on
+ the server side. (#5176)
+
+Deprecations and Removals
+-------------------------
+ - twisted.words.protocols.irc.IRCClient.me was deprecated in Twisted
+ 9.0 and has been removed. Use IRCClient.describe instead. (#5059)
+
+Other
+-----
+ - #5025, #5330
+
+
+Twisted Core 11.0.0 (2011-04-01)
+================================
+
+Features
+--------
+ - The reactor is not restartable, but it would previously fail to
+ complain. Now, when you restart an unrestartable reactor, you get
+ an exception. (#2066)
+ - twisted.plugin now only emits a short log message, rather than a
+ full traceback, if there is a problem writing out the dropin cache
+ file. (#2409)
+ - Added a 'replacement' parameter to the
+ 'twisted.python.deprecate.deprecated' decorator. This allows
+ deprecations to unambiguously specify what they have been
+ deprecated in favor of. (#3047)
+ - Added access methods to FilePath for FilePath.statinfo's st_ino,
+ st_dev, st_nlink, st_uid, and st_gid fields. This is in
+ preparation for the deprecation of FilePath.statinfo. (#4712)
+ - IPv4Address and UNIXAddress now have a __hash__ method. (#4783)
+ - twisted.protocols.ftp.FTP.ftp_STOR now catches `FTPCmdError`s
+ raised by the file writer, and returns the error back to the
+ client. (#4909)
+
+Bugfixes
+--------
+ - twistd will no longer fail if a non-root user passes --uid 'myuid'
+ as a command-line argument. Instead, it will emit an error message.
+ (#3172)
+ - IOCPReactor now sends immediate completions to the main loop
+ (#3233)
+ - trial can now load test methods from multiple classes, even if the
+ methods all happen to be inherited from the same base class.
+ (#3383)
+ - twisted.web.server will now produce a correct Allow header when a
+ particular render_FOO method is missing. (#3678)
+ - HEAD requests made to resources whose HEAD handling defaults to
+ calling render_GET now always receive a response with no body.
+ (#3684)
+ - trial now loads decorated test methods whether or not the decorator
+ preserves the original method name. (#3909)
+ - t.p.amp.AmpBox.serialize will now correctly consistently complain
+ when being fed Unicode. (#3931)
+ - twisted.internet.wxreactor now supports stopping more reliably.
+ (#3948)
+ - reactor.spawnProcess on Windows can now handle ASCII-encodable
+ Unicode strings in the system environment (#3964)
+ - When C-extensions are not complied for twisted, on python2.4, skip
+ a test in twisted.internet.test.test_process that may hang due to a
+ SIGCHLD related problem. Running 'python setup.py build_ext
+ --inplace' will compile the extension and cause the test to both
+ run and pass. (#4331)
+ - twisted.python.logfile.LogFile now raises a descriptive exception
+ when passed a log directoy which does not exist. (#4701)
+ - Fixed a bug where Inotify will fail to add a filepatch to watchlist
+ after it has been added/ignored previously. (#4708)
+ - IPv4Address and UNIXAddress object comparison operators fixed
+ (#4817)
+ - twisted.internet.task.Clock now sorts the list of pending calls
+ before and after processing each call (#4823)
+ - ConnectionLost is now in twisted.internet.error.__all__ instead of
+ twisted.words.protocols.jabber.xmlstream.__all__. (#4856)
+ - twisted.internet.process now detects the most appropriate mechanism
+ to use for detecting the open file descriptors on a system, getting
+ Twisted working on FreeBSD even when fdescfs is not mounted.
+ (#4881)
+ - twisted.words.services referenced nonexistent
+ twisted.words.protocols.irc.IRC_NOSUCHCHANNEL. This has been fixed.
+ Related code has also received test cases. (#4915)
+
+Improved Documentation
+----------------------
+ - The INSTALL file now lists all of Twisted's dependencies. (#967)
+ - Added the stopService and startService methods to all finger
+ example files. (#3375)
+ - Missing reactor.run() calls were added in the UDP and client howto
+ documents. (#3834)
+ - The maxRetries attribute of
+ twisted.internet.protocols.RetryingClientFactory now has API
+ documentation. (#4618)
+ - Lore docs pointed to a template that no longer existed, this has
+ been fixed. (#4682)
+ - The `servers` argument to `twisted.names.client.createResolver` now
+ has more complete API documentation. (#4713)
+ - Linked to the Twisted endpoints tutorial from the Twisted core
+ howto list. (#4773)
+ - The Endpoints howto now links to the API documentation. (#4774)
+ - The Quotes howto is now more clear in its PYTHONPATH setup
+ instructions. (#4785)
+ - The API documentation for DeferredList's fireOnOneCallback
+ parameter now gives the correct order of the elements of the result
+ tuple. (#4882)
+
+Deprecations and Removals
+-------------------------
+ - returning a value other than None from IProtocol.dataReceived was
+ deprecated (#2491)
+ - Deprecated the --extra option in trial. (#3372)
+ - twisted.protocols._c_urlarg has been removed. (#4162)
+ - Remove the --report-profile option for twistd, deprecated since
+ 2007. (#4236)
+ - Deprecated twisted.persisted.journal. This library is no longer
+ maintained. (#4298)
+ - Removed twisted.protocols.loopback.loopback, which has been
+ deprecated since Twisted 2.5. (#4547)
+ - __getitem__ __getslice__ and __eq__ (tuple comparison, indexing)
+ removed from twisted.internet.address.IPv4Address and
+ twisted.internet.address.UNIXAddress classes UNIXAddress and
+ IPv4Address properties _bwHack are now deprecated in
+ twisted.internet.address (#4817)
+ - twisted.python.reflect.allYourBase is now no longer used, replaced
+ with inspect.getmro (#4928)
+ - allYourBase and accumulateBases are now deprecated in favor of
+ inspect.getmro. (#4946)
+
+Other
+-----
+
+- #555, #1982, #2618, #2665, #2666, #4035, #4247, #4567, #4636,
+ #4717, #4733, #4750, #4821, #4842, #4846, #4853, #4857, #4858,
+ #4863, #4864, #4865, #4866, #4867, #4868, #4869, #4870, #4871,
+ #4872, #4873, #4874, #4875, #4876, #4877, #4878, #4879, #4905,
+ #4906, #4908, #4934, #4955, #4960
+
+
+Twisted Conch 11.0.0 (2011-04-01)
+=================================
+
+Bugfixes
+--------
+ - The transport for subsystem protocols now declares that it
+ implements ITransport and implements the getHost and getPeer
+ methods. (#2453)
+ - twisted.conch.ssh.transport.SSHTransportBase now responds to key
+ exchange messages at any time during a connection (instead of only
+ at connection setup). It also queues non-key exchange messages
+ sent during key exchange to avoid corrupting the connection state.
+ (#4395)
+ - Importing twisted.conch.ssh.common no longer breaks pow(base, exp[,
+ modulus]) when the gmpy package is installed and base is not an
+ integer. (#4803)
+ - twisted.conch.ls.lsLine now returns a time string which does not
+ consider the locale. (#4937)
+
+Improved Documentation
+----------------------
+ - Changed the man page for ckeygen to accurately reflect what it
+ does, and corrected its synposis so that a second "ckeygen" is not
+ a required part of the ckeygen command line. (#4738)
+
+Other
+-----
+ - #2112
+
+
+Twisted Lore 11.0.0 (2011-04-01)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Mail 11.0.0 (2011-04-01)
+================================
+
+Features
+--------
+ - The `twistd mail` command line now accepts endpoint descriptions
+ for POP3 and SMTP servers. (#4739)
+ - The twistd mail plugin now accepts new authentication options via
+ strcred.AuthOptionMixin. These include --auth, --auth-help, and
+ authentication type-specific help options. (#4740)
+
+Bugfixes
+--------
+ - twisted.mail.imap4.IMAP4Server now generates INTERNALDATE strings
+ which do not consider the locale. (#4937)
+
+Improved Documentation
+----------------------
+ - Added a simple SMTP example, showing how to use sendmail. (#4042)
+
+Other
+-----
+
+ - #4162
+
+
+Twisted Names 11.0.0 (2011-04-01)
+=================================
+
+No significant changes have been made for this release.
+
+
+Twisted News 11.0.0 (2011-04-01)
+================================
+
+No significant changes have been made for this release.
+
+Other
+-----
+ - #4580
+
+
+Twisted Pair 11.0.0 (2011-04-01)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Runner 11.0.0 (2011-04-01)
+==================================
+
+No significant changes have been made for this release.
+
+
+Twisted Web 11.0.0 (2011-04-01)
+===============================
+
+Features
+--------
+ - twisted.web._newclient.HTTPParser (and therefore Agent) now handles
+ HTTP headers delimited by bare LF newlines. (#3833)
+ - twisted.web.client.downloadPage now accepts the `afterFoundGet`
+ parameter, with the same meaning as the `getPage` parameter of the
+ same name. (#4364)
+ - twisted.web.xmlrpc.Proxy constructor now takes additional 'timeout'
+ and 'reactor' arguments. The 'timeout' argument defaults to 30
+ seconds. (#4741)
+ - Twisted Web now has a templating system, twisted.web.template,
+ which is a direct, simplified derivative of Divmod Nevow. (#4939)
+
+Bugfixes
+--------
+ - HTTPPageGetter now adds the port to the host header if it is not
+ the default for that scheme. (#3857)
+ - twisted.web.http.Request.write now raises an exception if it is
+ called after response generation has already finished. (#4317)
+ - twisted.web.client.HTTPPageGetter and twisted.web.client.getPage
+ now no longer make two requests when using afterFoundGet. (#4760)
+ - twisted.web.twcgi no longer adds an extra "content-type" header to
+ CGI responses. (#4786)
+ - twisted.web will now properly specify an encoding (UTF-8) on error,
+ redirect, and directory listing pages, so that IE7 and previous
+ will not improperly guess the 'utf7' encoding in these cases.
+ Please note that Twisted still sets a *default* content-type of
+ 'text/html', and you shouldn't rely on that: you should set the
+ encoding appropriately in your application. (#4900)
+ - twisted.web.http.Request.setHost now sets the port in the host
+ header if it is not the default. (#4918)
+ - default NOT_IMPLEMENTED and NOT_ALLOWED pages now quote the request
+ method and URI respectively, to protect against browsers which
+ don't quote those values for us. (#4978)
+
+Improved Documentation
+----------------------
+ - The XML-RPC howto now includes an example demonstrating how to
+ access the HTTP request object in a server-side XML-RPC method.
+ (#4732)
+ - The Twisted Web client howto now uses the correct, public name for
+ twisted.web.client.Response. (#4769)
+ - Some broken links were fixed, descriptions were updated, and new
+ API links were added in the Resource Templating documentation
+ (resource-templates.xhtml) (#4968)
+
+Other
+-----
+ - #2271, #2386, #4162, #4733, #4855, #4911, #4973
+
+
+Twisted Words 11.0.0 (2011-04-01)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient now has an invite method.
+ (#4820)
+
+Bugfixes
+--------
+ - twisted.words.protocols.irc.IRCClient.say is once again able to
+ send messages when using the default value for the length limit
+ argument. (#4758)
+ - twisted.words.protocols.jabber.jstrports is once again able to
+ parse jstrport description strings. (#4771)
+ - twisted.words.protocols.msn.NotificationClient now calls the
+ loginFailure callback when it is unable to connect to the Passport
+ server due to missing SSL dependencies. (#4801)
+ - twisted.words.protocols.jabber.xmpp_stringprep now always uses
+ Unicode version 3.2 for stringprep normalization. (#4850)
+
+Improved Documentation
+----------------------
+ - Removed the non-working AIM bot example, depending on the obsolete
+ twisted.words.protocols.toc functionality. (#4007)
+ - Outdated GUI-related information was removed from the IM howto.
+ (#4054)
+
+Deprecations and Removals
+-------------------------
+ - Remove twisted.words.protocols.toc, that was largely non-working
+ and useless since AOL disabled TOC on their AIM network. (#4363)
+
+Other
+-----
+ - #4733, #4902
+
+
+Twisted Core 10.2.0 (2010-11-29)
+================================
+
+Features
+--------
+ - twisted.internet.cfreactor has been significantly improved. It now
+ runs, and passes, the test suite. Many, many bugs in it have been
+ fixed, including several segfaults, as it now uses PyObjC and
+ longer requires C code in Twisted. (#1833)
+ - twisted.protocols.ftp.FTPRealm now accepts a parameter to override
+ "/home" as the container for user directories. The new
+ BaseFTPRealm class in the same module also allows easy
+ implementation of custom user directory schemes. (#2179)
+ - twisted.python.filepath.FilePath and twisted.python.zippath.ZipPath
+ now have a descendant method to simplify code which calls the child
+ method repeatedly. (#3169)
+ - twisted.python.failure._Frame objects now support fake f_locals
+ attribute. (#4045)
+ - twisted.internet.endpoints now has 'serverFromString' and
+ 'clientFromString' APIs for constructing endpoints from descriptive
+ strings. (#4473)
+ - The default trial reporter now combines reporting of tests with the
+ same result to shorten its summary output. (#4487)
+ - The new class twisted.protocols.ftp.SystemFTPRealm implements an
+ FTP realm which uses system accounts to select home directories.
+ (#4494)
+ - twisted.internet.reactor.spawnProcess now wastes less time trying
+ to close non-existent file descriptors on POSIX platforms. (#4522)
+ - twisted.internet.win32eventreactor now declares that it implements
+ a new twisted.internet.interfaces.IReactorWin32Events interface.
+ (#4523)
+ - twisted.application.service.IProcess now documents its attributes
+ using zope.interface.Attribute. (#4534)
+ - twisted.application.app.ReactorSelectionMixin now saves the value
+ of the --reactor option in the "reactor" key of the options object.
+ (#4563)
+ - twisted.internet.endpoints.serverFromString and clientFromString,
+ and therefore also twisted.application.strports.service, now
+ support plugins, so third parties may implement their own endpoint
+ types. (#4695)
+
+Bugfixes
+--------
+ - twisted.internet.defer.Deferred now handles chains iteratively
+ instead of recursively, preventing RuntimeError due to excessive
+ recursion when handling long Deferred chains. (#411)
+ - twisted.internet.cfreactor now works with trial. (#2556)
+ - twisted.enterprise.adbapi.ConnectionPool.close may now be called
+ even if the connection pool has not yet been started. This will
+ prevent the pool from ever starting. (#2680)
+ - twisted.protocols.basic.NetstringReceiver raises
+ NetstringParseErrors for invalid netstrings now. It handles empty
+ netstrings ("0:,") correctly, and the performance for receiving
+ netstrings has been improved. (#4378)
+ - reactor.listenUDP now returns an object which declares that it
+ implements IListeningPort. (#4462)
+ - twisted.python.randbytes no longer uses PyCrypto as a secure random
+ number source (since it is not one). (#4468)
+ - twisted.internet.main.installReactor now blocks installation of
+ another reactor when using python -O (#4476)
+ - twisted.python.deprecate.deprecatedModuleAttribute now emits only
+ one warning when used to deprecate a package attribute which is a
+ module. (#4492)
+ - The "brief" mode of twisted.python.failure.Failure.getTraceback now
+ handles exceptions raised by the underlying exception's __str__
+ method. (#4501)
+ - twisted.words.xish.domish now correctly parses XML with namespaces
+ which include whitespace. (#4503)
+ - twisted.names.authority.FileAuthority now generates correct
+ negative caching hints, marks its referral NS RRs as non-
+ authoritative, and correctly generates referrals for ALL_RECORDS
+ requests. (#4513)
+ - twisted.internet.test.reactormixins.ReactorBuilder's attribute
+ `requiredInterface` (which should an interface) is now
+ `requiredInterfaces` (a list of interfaces) as originally described
+ per the documentation. (#4527)
+ - twisted.python.zippath.ZipPath.__repr__ now correctly formats paths
+ with ".." in them (by including it). (#4535)
+ - twisted.names.hosts.searchFileFor has been fixed against
+ refcounting dependency. (#4540)
+ - The POSIX process transports now declare that they implement
+ IProcessTransport. (#4585)
+ - Twisted can now be built with the LLVM clang compiler, with
+ 'CC=clang python setup.py build'. C code that caused errors with
+ this compiler has been removed. (#4652)
+ - trial now puts coverage data in the path specified by --temp-
+ directory, even if that option comes after --coverage on the
+ command line. (#4657)
+ - The unregisterProducer method of connection-oriented transports
+ will now cause the connection to be closed if there was a prior
+ call to loseConnection. (#4719)
+ - Fixed an issue where the new StreamServerEndpointService didn't log
+ listen errors. (This was a bug not present in any previous
+ releases, as this class is new.) (#4731)
+
+Improved Documentation
+----------------------
+ - The trial man page now documents the meaning of the final line of
+ output of the default reporter. (#1384)
+ - The API documentation for twisted.internet.defer.DeferredList now
+ goes into more depth about the effects each of the __init__ flags
+ that class accepts. (#3595)
+ - There is now narrative documentation for the endpoints APIs, in the
+ 'endpoints' core howto, as well as modifications to the 'writing
+ clients' and 'writing servers' core howto documents to indicate
+ that endpoints are now the preferred style of listening and
+ connecting. (#4478)
+ - trial's man page now documents the --disablegc option in more
+ detail. (#4511)
+ - trial's coverage output format is now documented in the trial man
+ page. (#4512)
+ - Broken links and spelling errors in the finger tutorial are now
+ fixed. (#4516)
+ - twisted.internet.threads.blockingCallFromThread's docstring is now
+ explicit about Deferred support. (#4517)
+ - twisted.python.zippath.ZipPath.child now documents its handling of
+ ".." (which is not special, making it different from
+ FilePath.child). (#4535)
+ - The API docs for twisted.internet.defer.Deferred now cover several
+ more of its (less interesting) attributes. (#4538)
+ - LineReceiver, NetstringReceiver, and IntNStringReceiver from
+ twisted.protocols.basic now have improved API documentation for
+ read callbacks and write methods. (#4542)
+ - Tidied up the Twisted Conch documentation for easier conversion.
+ (#4566)
+ - Use correct Twisted version for when cancellation was introduced in
+ the Deferred docstring. (#4614)
+ - The logging howto is now more clear about how the standard library
+ logging module and twisted.python.log can be integrated. (#4642)
+ - The finger tutorial still had references to .tap files. This
+ reference has now been removed. The documentation clarifies
+ "finger.tap" is a module and not a filename. (#4679)
+ - The finger tutorial had a broken link to the
+ twisted.application.service.Service class, which is now fixed.
+ Additionally, a minor typo ('verison') was fixed. (#4681)
+ - twisted.protocols.policies.TimeoutMixin now has clearer API
+ documentation. (#4684)
+
+Deprecations and Removals
+-------------------------
+ - twisted.internet.defer.Deferred.setTimeout has been removed, after
+ being deprecated since Twisted 2.0. (#1702)
+ - twisted.internet.interfaces.IReactorTime.cancelCallLater
+ (deprecated since 2007) and
+ twisted.internet.interfaces.base.ReactorBase.cancelCallLater
+ (deprecated since 2002) have been removed. (#4076)
+ - Removed twisted.cred.util.py, which has been deprecated since
+ Twisted 8.3. (#4107)
+ - twisted.python.text.docstringLStrip was deprecated. (#4328)
+ - The module attributes `LENGTH`, `DATA`, `COMMA`, and `NUMBER` of
+ twisted.protocols.basic (previously used by `NetstringReceiver`)
+ are now deprecated. (#4541)
+ - twisted.protocols.basic.SafeNetstringReceiver, deprecated since
+ 2001 (before Twisted 2.0), was removed. (#4546)
+ - twisted.python.threadable.whenThreaded, deprecated since Twisted
+ 2.2.0, has been removed. (#4550)
+ - twisted.python.timeoutqueue, deprecated since Twisted 8.0, has been
+ removed. (#4551)
+ - iocpreactor transports can no longer be pickled. (#4617)
+
+Other
+-----
+ - #4300, #4475, #4477, #4504, #4556, #4562, #4564, #4569, #4608,
+ #4616, #4617, #4626, #4630, #4650, #4705
+
+
+Twisted Conch 10.2.0 (2010-11-29)
+=================================
+
+Bugfixes
+--------
+ - twisted.conch.ssh.factory.SSHFactory no longer disables coredumps.
+ (#2715)
+ - The Deferred returned by twisted.conch.telnet.TelnetTransport.will
+ now fires with an OptionRefused failure if the peer responds with a
+ refusal for the option negotiation. (#4231)
+ - SSHServerTransport and SSHClientTransport in
+ twisted.conch.ssh.transport no longer use PyCrypto to generate
+ random numbers for DH KEX. They also now generate values from the
+ full valid range, rather than only half of it. (#4469)
+ - twisted.conch.ssh.connection.SSHConnection now errbacks leftover
+ request deferreds on connection shutdown. (#4483)
+
+Other
+-----
+ - #4677
+
+
+Twisted Lore 10.2.0 (2010-11-29)
+================================
+
+No significant changes have been made for this release.
+
+Other
+-----
+ - #4571
+
+
+Twisted Mail 10.2.0 (2010-11-29)
+================================
+
+Improved Documentation
+----------------------
+ - The email server example now demonstrates how to set up
+ authentication and authorization using twisted.cred. (#4609)
+
+Deprecations and Removals
+-------------------------
+ - twisted.mail.smtp.sendEmail, deprecated since mid 2003 (before
+ Twisted 2.0), has been removed. (#4529)
+
+Other
+-----
+ - #4038, #4572
+
+
+Twisted Names 10.2.0 (2010-11-29)
+=================================
+
+Features
+--------
+ - twisted.names.server can now serve SPF resource records using
+ twisted.names.dns.Record_SPF. twisted.names.client can query for
+ them using lookupSenderPolicy. (#3928)
+
+Bugfixes
+--------
+ - twisted.names.common.extractRecords doesn't try to close the
+ transport anymore in case of recursion, as it's done by the
+ Resolver itself now. (#3998)
+
+Improved Documentation
+----------------------
+ - Tidied up the Twisted Names documentation for easier conversion.
+ (#4573)
+
+
+Twisted News 10.2.0 (2010-11-29)
+================================
+
+Bugfixes
+--------
+ - twisted.news.database.PickleStorage now invokes the email APIs
+ correctly, allowing it to actually send moderation emails. (#4528)
+
+
+Twisted Pair 10.2.0 (2010-11-29)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Runner 10.2.0 (2010-11-29)
+==================================
+
+No significant changes have been made for this release.
+
+
+Twisted Web 10.2.0 (2010-11-29)
+===============================
+
+Features
+--------
+ - twisted.web.xmlrpc.XMLRPC.xmlrpc_* methods can now be decorated
+ using withRequest to cause them to be passed the HTTP request
+ object. (#3073)
+
+Bugfixes
+--------
+ - twisted.web.xmlrpc.QueryProtocol.handleResponse now disconnects
+ from the server, meaning that Twisted XML-RPC clients disconnect
+ from the server as soon as they receive a response, rather than
+ relying on the server to disconnect. (#2518)
+ - twisted.web.twcgi now generates responses containing all
+ occurrences of duplicate headers produced by CGI scripts, not just
+ the last value. (#4742)
+
+Deprecations and Removals
+-------------------------
+ - twisted.web.trp, which has been deprecated since Twisted 9.0, was
+ removed. (#4299)
+
+Other
+-----
+ - #4576, #4577, #4709, #4723
+
+
+Twisted Words 10.2.0 (2010-11-29)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient.msg now enforces a maximum
+ length for messages, splitting up messages that are too long.
+ (#4416)
+
+Bugfixes
+--------
+ - twisted.words.protocols.irc.IRCClient no longer invokes privmsg()
+ in the default noticed() implementation. (#4419)
+ - twisted.words.im.ircsupport.IRCProto now sends the correct name in
+ the USER command. (#4641)
+
+Deprecations and Removals
+-------------------------
+ - Remove twisted.words.im.proxyui and twisted.words.im.tap. (#1823)
+
+
+Twisted Core 10.1.0 (2010-06-27)
+================================
+
+Features
+--------
+ - Add linux inotify support, allowing monitoring of file system
+ events. (#972)
+ - Deferreds now support cancellation. (#990)
+ - Added new "endpoint" interfaces in twisted.internet.interfaces,
+ which abstractly describe stream transport endpoints which can be
+ listened on or connected to. Implementations for TCP and SSL
+ clients and servers are present in twisted.internet.endpoints.
+ Notably, client endpoints' connect() methods return cancellable
+ Deferreds, so code written to use them can bypass the awkward
+ "ClientFactory.clientConnectionFailed" and
+ "Connector.stopConnecting" methods, and handle errbacks from or
+ cancel the returned deferred, respectively. (#1442)
+ - twisted.protocols.amp.Integer's documentation now clarifies that
+ integers of arbitrary size are supported and that the wire format
+ is a base-10 representation. (#2650)
+ - twisted.protocols.amp now includes support for transferring
+ timestamps (amp.DateTime) and decimal values (amp.Decimal). (#2651)
+ - twisted.protocol.ftp.IWriteFile now has a close() method, which can
+ return a Deferred. Previously a STOR command would finish
+ immediately upon the receipt of the last byte of the uploaded file.
+ With close(), the backend can delay the finish until it has
+ performed some other slow action (like storing the data to a
+ virtual filesystem). (#3462)
+ - FilePath now calls os.stat() only when new status information is
+ required, rather than immediately when anything changes. For some
+ applications this may result in fewer stat() calls. Additionally,
+ FilePath has a new method, 'changed', which applications may use to
+ indicate that the FilePath may have been changed on disk and
+ therefore the next status information request must fetch a new
+ stat result. This is useful if external systems, such as C
+ libraries, may have changed files that Twisted applications are
+ referencing via a FilePath. (#4130)
+ - Documentation improvements are now summarized in the NEWS file.
+ (#4224)
+ - twisted.internet.task.deferLater now returns a cancellable
+ Deferred. (#4318)
+ - The connect methods of twisted.internet.protocol.ClientCreator now
+ return cancellable Deferreds. (#4329)
+ - twisted.spread.pb now has documentation covering some of its
+ limitations. (#4402)
+ - twisted.spread.jelly now supports jellying and unjellying classes
+ defined with slots if they also implement __getstate__ and
+ __setstate__. (#4430)
+ - twisted.protocols.amp.ListOf arguments can now be specified as
+ optional. (#4474)
+
+Bugfixes
+--------
+ - On POSIX platforms, reactors now support child processes in a way
+ which doesn't cause other syscalls to sometimes fail with EINTR (if
+ running on Python 2.6 or if Twisted's extension modules have been
+ built). (#733)
+ - Substrings are escaped before being passed to a regular expression
+ for searching to ensure that they don't get interpreted as part of
+ the expression. (#1893)
+ - twisted.internet.stdio now supports stdout being redirected to a
+ normal file (except when using epollreactor). (#2259)
+ - (#2367)
+ - The tap2rpm script now works with modern versions of RPM. (#3292)
+ - twisted.python.modules.walkModules will now handle packages
+ explicitly precluded from importing by a None placed in
+ sys.modules. (#3419)
+ - ConnectedDatagramPort now uses stopListening when a connection
+ fails instead of the deprecated loseConnection. (#3425)
+ - twisted.python.filepath.FilePath.setContent is now safe for
+ multiple processes to use concurrently. (#3694)
+ - The mode argument to the methods of
+ twisted.internet.interfaces.IReactorUNIX is no longer deprecated.
+ (#4078)
+ - Do not include blacklisted projects when generating NEWS. (#4190)
+ - When generating NEWS for a project that had no significant changes,
+ include a section for that project and say that there were no
+ interesting changes. (#4191)
+ - Redundant 'b' mode is no longer passed to calls to FilePath.open
+ and FilePath.open itself now corrects the mode when multiple 'b'
+ characters are present, ensuring only one instance of 'b' is
+ provided, as a workaround for http://bugs.python.org/issue7686.
+ (#4207)
+ - HTML tags inside tags in the code snippets are now escaped.
+ (#4336)
+ - twisted.protocols.amp.CommandLocator now allows subclasses to
+ override responders inherited from base classes. (#4343)
+ - Fix a bunch of small but important defects in the INSTALL, README
+ and so forth. (#4346)
+ - The poll, epoll, glib2, and gtk2 reactors now all support half-
+ close in the twisted.internet.stdio.StandardIO transport. (#4352)
+ - twisted.application.internet no longer generates an extra and
+ invalid entry in its __all__ list for the nonexistent
+ MulticastClient. (#4373)
+ - Choosing a reactor documentation now says that only the select-
+ based reactor is a truly cross-platform reactor. (#4384)
+ - twisted.python.filepath.FilePath now no longer leaves files open,
+ to be closed by the garbage collector, when an exception is raised
+ in the implementation of setContent, getContent, or copyTo. (#4400)
+ - twisted.test.proto_helpers.StringTransport's getHost and getPeer
+ methods now return IPv4Address instances by default. (#4401)
+ - twisted.protocols.amp.BinaryBoxProtocol will no longer deliver an
+ empty string to a switched-to protocol's dataReceived method when
+ the BinaryBoxProtocol's buffer happened to be empty at the time of
+ the protocol switch. (#4405)
+ - IReactorUNIX.listenUNIX implementations now support abstract
+ namespace sockets on Linux. (#4421)
+ - Files opened with FilePath.create() (and therefore also files
+ opened via FilePath.open() on a path with alwaysCreate=True) will
+ now be opened in binary mode as advertised, so that they will
+ behave portably across platforms. (#4453)
+ - The subunit reporter now correctly reports import errors as errors,
+ rather than by crashing with an unrelated error. (#4496)
+
+Improved Documentation
+----------------------
+ - The finger tutorial example which introduces services now avoids
+ double-starting the loop to re-read its users file. (#4420)
+ - twisted.internet.defer.Deferred.callback's docstring now mentions
+ the implicit chaining feature. (#4439)
+ - doc/core/howto/listing/pb/chatclient.py can now actually send a
+ group message. (#4459)
+
+Deprecations and Removals
+-------------------------
+ - twisted.internet.interfaces.IReactorArbitrary,
+ twisted.application.internet.GenericServer, and
+ twisted.application.internet.GenericClient are now deprecated.
+ (#367)
+ - twisted.internet.gtkreactor is now deprecated. (#2833)
+ - twisted.trial.util.findObject has been deprecated. (#3108)
+ - twisted.python.threadpool.ThreadSafeList is deprecated and Jython
+ platform detection in Twisted core removed (#3725)
+ - twisted.internet.interfaces.IUDPConnectedTransport has been removed
+ (deprecated since Twisted 9.0). (#4077)
+ - Removed twisted.application.app.runWithProfiler, which has been
+ deprecated since Twisted 8.0. (#4090)
+ - Removed twisted.application.app.runWithHotshot, which has been
+ deprecated since Twisted 8.0. (#4091)
+ - Removed twisted.application.app.ApplicationRunner.startLogging,
+ which has been deprecated (doesn't say since when), as well as
+ support for the legacy
+ twisted.application.app.ApplicationRunner.getLogObserver method.
+ (#4092)
+ - twisted.application.app.reportProfile has been removed. (#4093)
+ - twisted.application.app.getLogFile has been removed. (#4094)
+ - Removed twisted.cred.util.py, which has been deprecated since
+ Twisted 8.3. (#4107)
+ - twisted.python.util.dsu is now deprecated. (#4339)
+ - In twisted.trial.util: FailureError, DirtyReactorWarning,
+ DirtyReactorError, and PendingTimedCallsError, which have all been
+ deprecated since Twisted 8.0, have been removed. (#4505)
+
+Other
+-----
+ - #1363, #1742, #3170, #3359, #3431, #3738, #4088, #4206, #4221,
+ #4239, #4257, #4272, #4274, #4287, #4291, #4293, #4309, #4316,
+ #4319, #4324, #4332, #4335, #4348, #4358, #4394, #4399, #4409,
+ #4418, #4443, #4449, #4479, #4485, #4486, #4497
+
+
+Twisted Conch 10.1.0 (2010-06-27)
+=================================
+
+Features
+--------
+ - twisted.conch.ssh.transport.SSHTransportBase now allows supported
+ ssh protocol versions to be overriden. (#4428)
+
+Bugfixes
+--------
+ - SSHSessionProcessProtocol now doesn't close the session when stdin
+ is closed, but instead when both stdout and stderr are. (#4350)
+ - The 'cftp' command-line tool will no longer encounter an
+ intermittent error, crashing at startup with a ZeroDivisionError
+ while trying to report progress. (#4463)
+ - twisted.conch.ssh.connection.SSHConnection now replies to requests
+ to open an unknown channel with a OPEN_UNKNOWN_CHANNEL_TYPE message
+ instead of closing the connection. (#4490)
+
+Deprecations and Removals
+-------------------------
+ - twisted.conch.insults.client was deprecated. (#4095)
+ - twisted.conch.insults.colors has been deprecated. Please use
+ twisted.conch.insults.helper instead. (#4096)
+ - Removed twisted.conch.ssh.asn1, which has been deprecated since
+ Twisted 9.0. (#4097)
+ - Removed twisted.conch.ssh.common.Entropy, as Entropy.get_bytes has
+ been deprecated since 2007 and Entropy.get_bytes was the only
+ attribute of Entropy. (#4098)
+ - Removed twisted.conch.ssh.keys.getPublicKeyString, which has been
+ deprecated since 2007. Also updated the conch examples
+ sshsimpleserver.py and sshsimpleclient.py to reflect this removal.
+ (#4099)
+ - Removed twisted.conch.ssh.keys.makePublicKeyString, which has been
+ deprecated since 2007. (#4100)
+ - Removed twisted.conch.ssh.keys.getPublicKeyObject, which has been
+ deprecated since 2007. (#4101)
+ - Removed twisted.conch.ssh.keys.getPrivateKeyObject, which has been
+ deprecated since 2007. Also updated the conch examples to reflect
+ this removal. (#4102)
+ - Removed twisted.conch.ssh.keys.makePrivateKeyString, which has been
+ deprecated since 2007. (#4103)
+ - Removed twisted.conch.ssh.keys.makePublicKeyBlob, which has been
+ deprecated since 2007. (#4104)
+ - Removed twisted.conch.ssh.keys.signData,
+ twisted.conch.ssh.keys.verifySignature, and
+ twisted.conch.ssh.keys.printKey, which have been deprecated since
+ 2007. (#4105)
+
+Other
+-----
+ - #3849, #4408, #4454
+
+
+Twisted Lore 10.1.0 (2010-06-27)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Mail 10.1.0 (2010-06-27)
+================================
+
+Bugfixes
+--------
+ - twisted.mail.imap4.IMAP4Server no longer fails on search queries
+ that contain wildcards. (#2278)
+ - A case which would cause twisted.mail.imap4.IMAP4Server to loop
+ indefinitely when handling a search command has been fixed. (#4385)
+
+Other
+-----
+ - #4069, #4271, #4467
+
+
+Twisted Names 10.1.0 (2010-06-27)
+=================================
+
+Features
+--------
+ - twisted.names.dns.Message now uses a specially constructed
+ dictionary for looking up record types. This yields a significant
+ performance improvement on PyPy. (#4283)
+
+
+Twisted News 10.1.0 (2010-06-27)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Pair 10.1.0 (2010-06-27)
+================================
+
+No significant changes have been made for this release.
+
+
+Twisted Runner 10.1.0 (2010-06-27)
+==================================
+
+Features
+--------
+ - twistd now has a procmon subcommand plugin - a convenient way to
+ monitor and automatically restart another process. (#4356)
+
+Deprecations and Removals
+-------------------------
+ - twisted.runner.procmon.ProcessMonitor's active, consistency, and
+ consistencyDelay attributes are now deprecated. (#1763)
+
+Other
+-----
+ - #3775
+
+
+Twisted Web 10.1.0 (2010-06-27)
+===============================
+
+Features
+--------
+ - twisted.web.xmlrpc.XMLRPC and twisted.web.xmlrpc.Proxy now expose
+ xmlrpclib's support of datetime.datetime objects if useDateTime is
+ set to True. (#3219)
+ - HTTP11ClientProtocol now has an abort() method for cancelling an
+ outstanding request by closing the connection before receiving the
+ entire response. (#3811)
+ - twisted.web.http_headers.Headers initializer now rejects
+ incorrectly structured dictionaries. (#4022)
+ - twisted.web.client.Agent now supports HTTPS URLs. (#4023)
+ - twisted.web.xmlrpc.Proxy.callRemote now returns a Deferred which
+ can be cancelled to abort the attempted XML-RPC call. (#4377)
+
+Bugfixes
+--------
+ - twisted.web.guard now logs out avatars even if a request completes
+ with an error. (#4411)
+ - twisted.web.xmlrpc.XMLRPC will now no longer trigger a RuntimeError
+ by trying to write responses to closed connections. (#4423)
+
+Improved Documentation
+----------------------
+ - Fix broken links to deliverBody and iweb.UNKNOWN_LENGTH in
+ doc/web/howto/client.xhtml. (#4507)
+
+Deprecations and Removals
+-------------------------
+ - twisted.web.twcgi.PHP3Script and twisted.web.twcgi.PHPScript are
+ now deprecated. (#516)
+
+Other
+-----
+ - #4403, #4452
+
+
+Twisted Words 10.1.0 (2010-06-27)
+=================================
+
+Bugfixes
+--------
+ - twisted.words.im.basechat.ChatUI now has a functional
+ contactChangedNick with unit tests. (#229)
+ - twisted.words.protocols.jabber.error.StanzaError now correctly sets
+ a default error type and code for the remote-server-timeout
+ condition (#4311)
+ - twisted.words.protocols.jabber.xmlstream.ListenAuthenticator now
+ uses unicode objects for session identifiers (#4345)
+
+
+Twisted Core 10.0.0 (2010-03-01)
+================================
+
+Features
+--------
+ - The twistd man page now has a SIGNALS section. (#689)
+
+ - reactor.spawnProcess now will not emit a PotentialZombieWarning
+ when called before reactor.run, and there will be no potential for
+ zombie processes in this case. (#2078)
+
+ - High-throughput applications based on Perspective Broker should now
+ run noticably faster thanks to the use of a more efficient decoding
+ function in Twisted Spread. (#2310)
+
+ - Documentation for trac-post-commit-hook functionality in svn-dev
+ policy. (#3867)
+
+ - twisted.protocols.socks.SOCKSv4 now supports the SOCKSv4a protocol.
+ (#3886)
+
+ - Trial can now output test results according to the subunit
+ protocol, as long as Subunit is installed (see
+ https://launchpad.net/subunit). (#4004)
+
+ - twisted.protocols.amp now provides a ListOf argument type which can
+ be composed with some other argument types to create a zero or more
+ element sequence of that type. (#4116)
+
+ - If returnValue is invoked outside of a function decorated with
+ @inlineCallbacks, but causes a function thusly decorated to exit, a
+ DeprecationWarning will be emitted explaining this potentially
+ confusing behavior. In a future release, this will cause an
+ exception. (#4157)
+
+ - twisted.python.logfile.BaseLogFile now has a reopen method allowing
+ you to use an external logrotate mechanism. (#4255)
+
+Bugfixes
+--------
+ - FTP.ftp_NLST now handles requests on invalid paths in a way
+ consistent with RFC 959. (#1342)
+
+ - twisted.python.util.initgroups now calls the low-level C initgroups
+ by default if available: the python version can create lots of I/O
+ with certain authentication setup to retrieve all the necessary
+ information. (#3226)
+
+ - startLogging now does nothing on subsequent invocations, thus
+ fixing a terrible infinite recursion bug that's only on edge case.
+ (#3289)
+
+ - Stringify non-string data to NetstringReceiver.sendString before
+ calculating the length so that the calculated length is equal to
+ the actual length of the transported data. (#3299)
+
+ - twisted.python.win32.cmdLineQuote now correctly quotes empty
+ strings arguments (#3876)
+
+ - Change the behavior of the Gtk2Reactor to register only one source
+ watch for each file descriptor, instead of one for reading and one
+ for writing. In particular, it fixes a bug with Glib under Windows
+ where we failed to notify when a client is connected. (#3925)
+
+ - Twisted Trial no longer crashes if it can't remove an old
+ _trial_temp directory. (#4020)
+
+ - The optional _c_urlarg extension now handles unquote("") correctly
+ on platforms where malloc(0) returns NULL, such as AIX. It also
+ compiles with less warnings. (#4142)
+
+ - On POSIX, child processes created with reactor.spawnProcess will no
+ longer automatically ignore the signals which the parent process
+ has set to be ignored. (#4199)
+
+ - All SOCKSv4a tests now use a dummy reactor with a deterministic
+ resolve method. (#4275)
+
+ - Prevent extraneous server, date and content-type headers in proxy
+ responses. (#4277)
+
+Deprecations and Removals
+-------------------------
+ - twisted.internet.error.PotentialZombieWarning is now deprecated.
+ (#2078)
+
+ - twisted.test.time_helpers is now deprecated. (#3719)
+
+ - The deprecated connectUDP method of IReactorUDP has now been
+ removed. (#4075)
+
+ - twisted.trial.unittest.TestCase now ignores the previously
+ deprecated setUpClass and tearDownClass methods. (#4175)
+
+Other
+-----
+ - #917, #2406, #2481, #2608, #2689, #2884, #3056, #3082, #3199,
+ #3480, #3592, #3718, #3935, #4066, #4083, #4154, #4166, #4169,
+ #4176, #4183, #4186, #4188, #4189, #4194, #4201, #4204, #4209,
+ #4222, #4234, #4235, #4238, #4240, #4245, #4251, #4264, #4268,
+ #4269, #4282
+
+
+Twisted Conch 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.conch.checkers.SSHPublicKeyDatabase now looks in the
+ correct user directory for authorized_keys files. (#3984)
+ - twisted.conch.ssh.SSHUserAuthClient now honors preferredOrder when
+ authenticating. (#4266)
+
+Other
+-----
+ - #2391, #4203, #4265
+
+
+Twisted Lore 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4241
+
+
+Twisted Mail 10.0.0 (2010-03-01)
+================================
+
+Bugfixes
+--------
+ - twisted.mail.smtp.ESMTPClient and
+ twisted.mail.smtp.LOGINAuthenticator now implement the (obsolete)
+ LOGIN SASL mechanism according to the draft specification. (#4031)
+
+ - twisted.mail.imap4.IMAP4Client will no longer misparse all html-
+ formatted message bodies received in response to a fetch command.
+ (#4049)
+
+ - The regression in IMAP4 search handling of "OR" and "NOT" terms has
+ been fixed. (#4178)
+
+Other
+-----
+ - #4028, #4170, #4200
+
+
+Twisted Names 10.0.0 (2010-03-01)
+=================================
+
+Bugfixes
+--------
+ - twisted.names.root.Resolver no longer leaks UDP sockets while
+ resolving names. (#970)
+
+Deprecations and Removals
+-------------------------
+ - Several top-level functions in twisted.names.root are now
+ deprecated. (#970)
+
+Other
+-----
+ - #4066
+
+
+Twisted Pair 10.0.0 (2010-03-01)
+================================
+
+Other
+-----
+ - #4170
+
+
+Twisted Runner 10.0.0 (2010-03-01)
+==================================
+
+Other
+-----
+ - #3961
+
+
+Twisted Web 10.0.0 (2010-03-01)
+===============================
+
+Features
+--------
+ - Twisted Web in 60 Seconds, a series of short tutorials with self-
+ contained examples on a range of common web topics, is now a part
+ of the Twisted Web howto documentation. (#4192)
+
+Bugfixes
+--------
+ - Data and File from twisted.web.static and
+ twisted.web.distrib.UserDirectory will now only generate a 200
+ response for GET or HEAD requests.
+ twisted.web.client.HTTPPageGetter will no longer ignore the case of
+ a request method when considering whether to apply special HEAD
+ processing to a response. (#446)
+
+ - twisted.web.http.HTTPClient now supports multi-line headers.
+ (#2062)
+
+ - Resources served via twisted.web.distrib will no longer encounter a
+ Banana error when writing more than 640kB at once to the request
+ object. (#3212)
+
+ - The Error, PageRedirect, and InfiniteRedirection exception in
+ twisted.web now initialize an empty message parameter by mapping
+ the HTTP status code parameter to a descriptive string. Previously
+ the lookup would always fail, leaving message empty. (#3806)
+
+ - The 'wsgi.input' WSGI environment object now supports -1 and None
+ as arguments to the read and readlines methods. (#4114)
+
+ - twisted.web.wsgi doesn't unquote QUERY_STRING anymore, thus
+ complying with the WSGI reference implementation. (#4143)
+
+ - The HTTP proxy will no longer pass on keep-alive request headers
+ from the client, preventing pages from loading then "hanging"
+ (leaving the connection open with no hope of termination). (#4179)
+
+Deprecations and Removals
+-------------------------
+ - Remove '--static' option from twistd web, that served as an alias
+ for the '--path' option. (#3907)
+
+Other
+-----
+ - #3784, #4216, #4242
+
+
+Twisted Words 10.0.0 (2010-03-01)
+=================================
+
+Features
+--------
+ - twisted.words.protocols.irc.IRCClient.irc_MODE now takes ISUPPORT
+ parameters into account when parsing mode messages with arguments
+ that take parameters (#3296)
+
+Bugfixes
+--------
+ - When twisted.words.protocols.irc.IRCClient's versionNum and
+ versionEnv attributes are set to None, they will no longer be
+ included in the client's response to CTCP VERSION queries. (#3660)
+
+ - twisted.words.protocols.jabber.xmlstream.hashPassword now only
+ accepts unicode as input (#3741, #3742, #3847)
+
+Other
+-----
+ - #2503, #4066, #4261
+
+
+Twisted Core 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - LineReceiver.clearLineBuffer now returns the bytes that it cleared (#3573)
+ - twisted.protocols.amp now raises InvalidSignature when bad arguments are
+ passed to Command.makeArguments (#2808)
+ - IArgumentType was added to represent an existing but previously unspecified
+ interface in amp (#3468)
+ - Obscure python tricks have been removed from the finger tutorials (#2110)
+ - The digest auth implementations in twisted.web and twisted.protocolos.sip
+ have been merged together in twisted.cred (#3575)
+ - FilePath and ZipPath now has a parents() method which iterates up all of its
+ parents (#3588)
+ - reactors which support threads now have a getThreadPool method (#3591)
+ - The MemCache client implementation now allows arguments to the "stats"
+ command (#3661)
+ - The MemCache client now has a getMultiple method which allows fetching of
+ multiple values (#3171)
+ - twisted.spread.jelly can now unserialize some new-style classes (#2950)
+ - twisted.protocols.loopback.loopbackAsync now accepts a parameter to control
+ the data passed between client and server (#3820)
+ - The IOCP reactor now supports SSL (#593)
+ - Tasks in a twisted.internet.task.Cooperator can now be paused, resumed, and
+ cancelled (#2712)
+ - AmpList arguments can now be made optional (#3891)
+ - The syslog output observer now supports log levels (#3300)
+ - LoopingCall now supports reporting the number of intervals missed if it
+ isn't able to schedule calls fast enough (#3671)
+
+Fixes
+-----
+ - The deprecated md5 and sha modules are no longer used if the stdlib hashlib
+ module is available (#2763)
+ - An obscure deadlock involving waking up the reactor within signal handlers
+ in particular threads was fixed (#1997)
+ - The passivePortRange attribute of FTPFactory is now honored (#3593)
+ - TestCase.flushWarnings now flushes warnings even if they were produced by a
+ file that was renamed since it was byte compiled (#3598)
+ - Some internal file descriptors are now marked as close-on-exec, so these will
+ no longer be leaked to child processes (#3576)
+ - twisted.python.zipstream now correctly extracts the first file in a directory
+ as a file, and not an empty directory (#3625)
+ - proxyForInterface now returns classes which correctly *implement* interfaces
+ rather than *providing* them (#3646)
+ - SIP Via header parameters should now be correctly generated (#2194)
+ - The Deferred returned by stopListening would sometimes previously never fire
+ if an exception was raised by the underlying file descriptor's connectionLost
+ method. Now the Deferred will fire with a failure (#3654)
+ - The command-line tool "manhole" should now work with newer versions of pygtk
+ (#2464)
+ - When a DefaultOpenSSLContextFactory is instantiated with invalid parameters,
+ it will now raise an exception immediately instead of waiting for the first
+ connection (#3700)
+ - Twisted command line scripts should now work when installed in a virtualenv
+ (#3750)
+ - Trial will no longer delete temp directories which it did not create (#3481)
+ - Processes started on Windows should now be cleaned up properly in more cases
+ (#3893)
+ - Certain misbehaving importers will no longer cause twisted.python.modules
+ (and thus trial) to raise an exception, but rather issue a warning (#3913)
+ - MemCache client protocol methods will now fail when the transport has been
+ disconnected (#3643)
+ - In the AMP method callRemoteString, the requiresAnswer parameter is now
+ honored (#3999)
+ - Spawning a "script" (a file which starts with a #! line) on Windows running
+ Python 2.6 will now work instead of raising an exception about file mode
+ "ru" (#3567)
+ - FilePath's walk method now calls its "descend" parameter even on the first
+ level of children, instead of only on grandchildren. This allows for better
+ symlink cycle detection (#3911)
+ - Attempting to write unicode data to process pipes on Windows will no longer
+ result in arbitrarily encoded messages being written to the pipe, but instead
+ will immediately raise an error (#3930)
+ - The various twisted command line utilities will no longer print
+ ModuleType.__doc__ when Twisted was installed with setuptools (#4030)
+ - A Failure object will now be passed to connectionLost on stdio connections
+ on Windows, instead of an Exception object (#3922)
+
+Deprecations and Removals
+-------------------------
+ - twisted.persisted.marmalade was deleted after a long period of deprecation
+ (#876)
+ - Some remaining references to the long-gone plugins.tml system were removed
+ (#3246)
+ - SSLv2 is now disabled by default, but it can be re-enabled explicitly
+ (#3330)
+ - twisted.python.plugin has been removed (#1911)
+ - reactor.run will now raise a ReactorAlreadyRunning exception when it is
+ called reentrantly instead of warning a DeprecationWarning (#1785)
+ - twisted.spread.refpath is now deprecated because it is unmaintained,
+ untested, and has dubious value (#3723)
+ - The unused --quiet flag has been removed from the twistd command (#3003)
+
+Other
+-----
+ - #3545, #3490, #3544, #3537, #3455, #3315, #2281, #3564, #3570, #3571, #3486,
+ #3241, #3599, #3220, #1522, #3611, #3596, #3606, #3609, #3602, #3637, #3647,
+ #3632, #3675, #3673, #3686, #2217, #3685, #3688, #2456, #506, #3635, #2153,
+ #3581, #3708, #3714, #3717, #3698, #3747, #3704, #3707, #3713, #3720, #3692,
+ #3376, #3652, #3695, #3735, #3786, #3783, #3699, #3340, #3810, #3822, #3817,
+ #3791, #3859, #2459, #3677, #3883, #3894, #3861, #3822, #3852, #3875, #2722,
+ #3768, #3914, #3885, #2719, #3905, #3942, #2820, #3990, #3954, #1627, #2326,
+ #2972, #3253, #3937, #4058, #1200, #3639, #4079, #4063, #4050
+
+
+Twisted Conch 9.0.0 (2009-11-24)
+================================
+
+Fixes
+-----
+ - The SSH key parser has been removed and conch now uses pyASN1 to parse keys.
+ This should fix a number of cases where parsing a key would fail, but it now
+ requires users to have pyASN1 installed (#3391)
+ - The time field on SFTP file listings should now be correct (#3503)
+ - The day field on SFTP file listings should now be correct on Windows (#3503)
+ - The "cftp" sftp client now truncates files it is uploading over (#2519)
+ - The telnet server protocol can now properly respond to subnegotiation
+ requests (#3655)
+ - Tests and factoring of the SSHv2 server implementation are now much better
+ (#2682)
+ - The SSHv2 server now sends "exit-signal" messages to the client, instead of
+ raising an exception, when a process dies due to a signal (#2687)
+ - cftp's client-side "exec" command now uses /bin/sh if the current user has
+ no shell (#3914)
+
+Deprecations and Removals
+-------------------------
+ - The buggy SSH connection sharing feature of the SSHv2 client was removed
+ (#3498)
+ - Use of strings and PyCrypto objects to represent keys is deprecated in favor
+ of using Conch Key objects (#2682)
+
+Other
+-----
+ - #3548, #3537, #3551, #3220, #3568, #3689, #3709, #3809, #2763, #3540, #3750,
+ #3897, #3813, #3871, #3916, #4047, #3940, #4050
+
+
+Twisted Lore 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - Python source listings now include line numbers (#3486)
+
+Fixes
+-----
+ - Lore now uses minidom instead of Twisted's microdom, which incidentally
+ fixes some Lore bugs such as throwing away certain whitespace
+ (#3560, #414, #3619)
+ - Lore's "lint" command should no longer break on documents with links in them
+ (#4051, #4115)
+
+Deprecations and Removals
+-------------------------
+ - Lore no longer uses the ancient "tml" Twisted plugin system (#1911)
+
+Other
+-----
+ - #3565, #3246, #3540, #3750, #4050
+
+
+Twisted Mail 9.0.0 (2009-11-24)
+===============================
+
+Features
+--------
+ - maildir.StringListMailbox, an in-memory maildir mailbox, now supports
+ deletion, undeletion, and syncing (#3547)
+ - SMTPClient's callbacks are now more completely documented (#684)
+
+Fixes
+-----
+ - Parse UNSEEN response data and include it in the result of
+ IMAP4Client.examine (#3550)
+ - The IMAP4 client now delivers more unsolicited server responses to callbacks
+ rather than ignoring them, and also won't ignore solicited responses that
+ arrive on the same line as an unsolicited one (#1105)
+ - Several bugs in the SMTP client's idle timeout support were fixed (#3641,
+ #1219)
+ - A case where the SMTP client could skip some recipients when retrying
+ delivery has been fixed (#3638)
+ - Errors during certain data transfers will no longer be swallowed. They will
+ now bubble up to the higher-level API (such as the sendmail function) (#3642)
+ - Escape sequences inside quoted strings in IMAP4 should now be parsed
+ correctly by the IMAP4 server protocol (#3659)
+ - The "imap4-utf-7" codec that is registered by twisted.mail.imap4 had a number
+ of fixes that allow it to work better with the Python codecs system, and to
+ actually work (#3663)
+ - The Maildir implementation now ensures time-based ordering of filenames so
+ that the lexical sorting of messages matches the order in which they were
+ received (#3812)
+ - SASL PLAIN credentials generated by the IMAP4 protocol implementations
+ (client and server) should now be RFC-compliant (#3939)
+ - Searching for a set of sequences using the IMAP4 "SEARCH" command should
+ now work on the IMAP4 server protocol implementation. This at least improves
+ support for the Pine mail client (#1977)
+
+Other
+-----
+ - #2763, #3647, #3750, #3819, #3540, #3846, #2023, #4050
+
+
+Twisted Names 9.0.0 (2009-11-24)
+================================
+
+Deprecations and Removals
+-------------------------
+ - client.ThreadedResolver is deprecated in favor of
+ twisted.internet.base.ThreadedResolver (#3710)
+
+Other
+-----
+ - #3540, #3560, #3712, #3750, #3990
+
+
+Twisted News 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #2763, #3540
+
+
+Twisted Pair 9.0.0 (2009-11-24)
+===============================
+
+Other
+-----
+ - #3540, #4050
+
+
+Twisted Runner 9.0.0 (2009-11-24)
+=================================
+
+Features
+--------
+ - procmon.ProcessMonitor.addProcess now accepts an 'env' parameter which allows
+ users to specify the environment in which a process will be run (#3691)
+
+Other
+-----
+ - #3540
+
+
+Twisted Web 9.0.0 (2009-11-24)
+==============================
+
+Features
+--------
+ - There is now an iweb.IRequest interface which specifies the interface that
+ request objects provide (#3416)
+ - downloadPage now supports the same cookie, redirect, and timeout features
+ that getPage supports (#2971)
+ - A chapter about WSGI has been added to the twisted.web documentation (#3510)
+ - The HTTP auth support in the web server now allows anonymous sessions by
+ logging in with ANONYMOUS credentials when no Authorization header is
+ provided in a request (#3924, #3936)
+ - HTTPClientFactory now accepts a parameter to enable a common deviation from
+ the HTTP 1.1 standard by responding to redirects in a POSTed request with a
+ GET instead of another POST (#3624)
+ - A new basic HTTP/1.1 client API is included in twisted.web.client.Agent
+ (#886, #3987)
+
+Fixes
+-----
+ - Requests for "insecure" children of a static.File (such as paths containing
+ encoded directory separators) will now result in a 404 instead of a 500
+ (#3549, #3469)
+ - When specifying a followRedirect argument to the getPage function, the state
+ of redirect-following for other getPage calls should now be unaffected. It
+ was previously overwriting a class attribute which would affect outstanding
+ getPage calls (#3192)
+ - Downloading an URL of the form "http://example.com:/" will now work,
+ ignoring the extraneous colon (#2402)
+ - microdom's appendChild method will no longer issue a spurious warning, and
+ microdom's methods in general should now issue more meaningful exceptions
+ when invalid parameters are passed (#3421)
+ - WSGI applications will no longer have spurious Content-Type headers added to
+ their responses by the twisted.web server. In addition, WSGI applications
+ will no longer be able to specify the server-restricted headers Server and
+ Date (#3569)
+ - http_headers.Headers now normalizes the case of raw headers passed directly
+ to it in the same way that it normalizes the headers passed to setRawHeaders
+ (#3557)
+ - The distrib module no longer relies on the deprecated woven package (#3559)
+ - twisted.web.domhelpers now works with both microdom and minidom (#3600)
+ - twisted.web servers will now ignore invalid If-Modified-Since headers instead
+ of returning a 500 error (#3601)
+ - Certain request-bound memory and file resources are cleaned up slightly
+ sooner by the request when the connection is lost (#1621, #3176)
+ - xmlrpclib.DateTime objects should now correctly round-trip over twisted.web's
+ XMLRPC support in all supported versions of Python, and errors during error
+ serialization will no longer hang a twisted.web XMLRPC response (#2446)
+ - request.content should now always be seeked to the beginning when
+ request.process is called, so application code should never need to seek
+ back manually (#3585)
+ - Fetching a child of static.File with a double-slash in the URL (such as
+ "example//foo.html") should now return a 404 instead of a traceback and
+ 500 error (#3631)
+ - downloadPage will now fire a Failure on its returned Deferred instead of
+ indicating success when the connection is prematurely lost (#3645)
+ - static.File will now provide a 404 instead of a 500 error when it was
+ constructed with a non-existent file (#3634)
+ - microdom should now serialize namespaces correctly (#3672)
+ - The HTTP Auth support resource wrapper should no longer corrupt requests and
+ cause them to skip a segment in the request path (#3679)
+ - The twisted.web WSGI support should now include leading slashes in PATH_INFO,
+ and SCRIPT_NAME will be empty if the application is at the root of the
+ resource tree. This means that WSGI applications should no longer generate
+ URLs with double-slashes in them even if they naively concatenate the values
+ (#3721)
+ - WSGI applications should now receive the requesting client's IP in the
+ REMOTE_ADDR environment variable (#3730)
+ - The distrib module should work again. It was unfortunately broken with the
+ refactoring of twisted.web's header support (#3697)
+ - static.File now supports multiple ranges specified in the Range header
+ (#3574)
+ - static.File should now generate a correct Content-Length value when the
+ requested Range value doesn't fit entirely within the file's contents (#3814)
+ - Attempting to call request.finish() after the connection has been lost will
+ now immediately raise a RuntimeError (#4013)
+ - An HTTP-auth resource should now be able to directly render the wrapped
+ avatar, whereas before it would only allow retrieval of child resources
+ (#4014)
+ - twisted.web's wsgi support should no longer attempt to call request.finish
+ twice, which would cause errors in certain cases (#4025)
+ - WSGI applications should now be able to handle requests with large bodies
+ (#4029)
+ - Exceptions raised from WSGI applications should now more reliably be turned
+ into 500 errors on the HTTP level (#4019)
+ - DeferredResource now correctly passes through exceptions raised from the
+ wrapped resource, instead of turning them all into 500 errors (#3932)
+ - Agent.request now generates a Host header when no headers are passed at
+ (#4131)
+
+Deprecations and Removals
+-------------------------
+ - The unmaintained and untested twisted.web.monitor module was removed (#2763)
+ - The twisted.web.woven package has been removed (#1522)
+ - All of the error resources in twisted.web.error are now in
+ twisted.web.resource, and accessing them through twisted.web.error is now
+ deprecated (#3035)
+ - To facilitate a simplification of the timeout logic in server.Session,
+ various things have been deprecated (#3457)
+ - the loopFactory attribute is now ignored
+ - the checkExpired method now does nothing
+ - the lifetime parameter to startCheckingExpiration is now ignored
+ - The twisted.web.trp module is now deprecated (#2030)
+
+Other
+-----
+ - #2763, #3540, #3575, #3610, #3605, #1176, #3539, #3750, #3761, #3779, #2677,
+ #3782, #3904, #3919, #3418, #3990, #1404, #4050
+
+
+Twisted Words 9.0.0 (2009-11-24)
+================================
+
+Features
+--------
+ - IRCClient.describe is a new method meant to replace IRCClient.me to send
+ CTCP ACTION messages with less confusing behavior (#3910)
+ - The XMPP client protocol implementation now supports ANONYMOUS SASL
+ authentication (#4067)
+ - The IRC client protocol implementation now has better support for the
+ ISUPPORT server->client message, storing the data in a new
+ ServerSupportedFeatures object accessible via IRCClient.supported (#3285)
+
+Fixes
+-----
+ - The twisted.words IRC server now always sends an MOTD, which at least makes
+ Pidgin able to successfully connect to a twisted.words IRC server (#2385)
+ - The IRC client will now dispatch "RPL MOTD" messages received before a
+ "RPL MOTD START" instead of raising an exception (#3676)
+ - The IRC client protocol implementation no longer updates its 'nickname'
+ attribute directly; instead, that attribute will be updated when the server
+ acknowledges the change (#3377)
+ - The IRC client protocol implementation now supports falling back to another
+ nickname when a nick change request fails (#3377, #4010)
+
+Deprecations and Removals
+-------------------------
+ - The TOC protocol implementation is now deprecated, since the protocol itself
+ has been deprecated and obselete for quite a long time (#3580)
+ - The gui "im" application has been removed, since it relied on GTK1, which is
+ hard to find these days (#3699, #3340)
+
+Other
+-----
+ - #2763, #3540, #3647, #3750, #3895, #3968, #4050
+
+
+Core 8.2.0 (2008-12-16)
+=======================
+
+Features
+--------
+ - Reactors are slowly but surely becoming more isolated, thus improving
+ testability (#3198)
+ - FilePath has gained a realpath method, and FilePath.walk no longer infinitely
+ recurses in the case of a symlink causing a self-recursing filesystem tree
+ (#3098)
+ - FilePath's moveTo and copyTo methods now have an option to disable following
+ of symlinks (#3105)
+ - Private APIs are now included in the API documentation (#3268)
+ - hotshot is now the default profiler for the twistd --profile parameter and
+ using cProfile is now documented (#3355, #3356)
+ - Process protocols can now implement a processExited method, which is
+ distinct from processEnded in that it is called immediately when the child
+ has died, instead of waiting for all the file descriptors to be closed
+ (#1291)
+ - twistd now has a --umask option (#966, #3024)
+ - A new deferToThreadPool function exists in twisted.internet.threads (#2845)
+ - There is now an example of writing an FTP server in examples/ftpserver.py
+ (#1579)
+ - A new runAsEffectiveUser function has been added to twisted.python.util
+ (#2607)
+ - twisted.internet.utils.getProcessOutput now offers a mechanism for
+ waiting for the process to actually end, in the event of data received on
+ stderr (#3239)
+ - A fullyQualifiedName function has been added to twisted.python.reflect
+ (#3254)
+ - strports now defaults to managing access to a UNIX socket with a lock;
+ lockfile=0 can be included in the strports specifier to disable this
+ behavior (#2295)
+ - FTPClient now has a 'rename' method (#3335)
+ - FTPClient now has a 'makeDirectory' method (#3500)
+ - FTPClient now has a 'removeFile' method (#3491)
+ - flushWarnings, A new Trial method for testing warnings, has been added
+ (#3487, #3427, #3506)
+ - The log observer can now be configured in .tac files (#3534)
+
+Fixes
+-----
+ - TLS Session Tickets are now disabled by default, allowing connections to
+ certain servers which hang when an empty session ticket is received (like
+ GTalk) (#3463)
+ - twisted.enterprise.adbapi.ConnectionPool's noisy attribute now defaults to
+ False, as documented (#1806)
+ - Error handling and logging in adbapi is now much improved (#3244)
+ - TCP listeners can now be restarted (#2913)
+ - Doctests can now be rerun with trial's --until-failure option (#2713)
+ - Some memory leaks have been fixed in trial's --until-failure
+ implementation (#3119, #3269)
+ - Trial's summary reporter now prints correct runtime information and handles
+ the case of 0 tests (#3184)
+ - Trial and any other user of the 'namedAny' function now has better error
+ reporting in the case of invalid module names (#3259)
+ - Multiple instances of trial can now run in parallel in the same directory
+ by creating _trial_temp directories with an incremental suffix (#2338)
+ - Trial's failUnlessWarns method now works on Python 2.6 (#3223)
+ - twisted.python.log now hooks into the warnings system in a way compatible
+ with Python 2.6 (#3211)
+ - The GTK2 reactor is now better supported on Windows, but still not passing
+ the entire test suite (#3203)
+ - low-level failure handling in spawnProcess has been improved and no longer
+ leaks file descriptors (#2305, #1410)
+ - Perspective Broker avatars now have their logout functions called in more
+ cases (#392)
+ - Log observers which raise exceptions are no longer removed (#1069)
+ - transport.getPeer now always includes an IP address in the Address returned
+ instead of a hostname (#3059)
+ - Functions in twisted.internet.utils which spawn processes now avoid calling
+ chdir in the case where no working directory is passed, to avoid some
+ obscure permission errors (#3159)
+ - twisted.spread.publish.Publishable no longer corrupts line endings on
+ Windows (#2327)
+ - SelectReactor now properly detects when a TLS/TCP connection has been
+ disconnected (#3218)
+ - twisted.python.lockfile no longer raises an EEXIST OSError and is much
+ better supported on Windows (#3367)
+ - When ITLSTransport.startTLS is called while there is data in the write
+ buffer, TLS negotiation will now be delayed instead of the method raising
+ an exception (#686)
+ - The userAnonymous argument to FTPFactory is now honored (#3390)
+ - twisted.python.modules no longer tries to "fix" sys.modules after an import
+ error, which was just causing problems (#3388)
+ - setup.py no longer attempts to build extension modules when run with Jython
+ (#3410)
+ - AMP boxes can now be sent in IBoxReceiver.startReceivingBoxes (#3477)
+ - AMP connections are closed as soon as a key length larger than 255 is
+ received (#3478)
+ - Log events with timezone offsets between -1 and -59 minutes are now
+ correctly reported as negative (#3515)
+
+Deprecations and Removals
+-------------------------
+ - Trial's setUpClass and tearDownClass methods are now deprecated (#2903)
+ - problemsFromTransport has been removed in favor of the argument passed to
+ connectionLost (#2874)
+ - The mode parameter to methods of IReactorUNIX and IReactorUNIXDatagram are
+ deprecated in favor of applications taking other security precautions, since
+ the mode of a Unix socket is often not respected (#1068)
+ - Index access on instances of twisted.internet.defer.FirstError has been
+ removed in favor of the subFailure attribute (#3298)
+ - The 'changeDirectory' method of FTPClient has been deprecated in favor of
+ the 'cwd' method (#3491)
+
+Other
+-----
+
+ - #3202, #2869, #3225, #2955, #3237, #3196, #2355, #2881, #3054, #2374, #2918,
+ #3210, #3052, #3267, #3288, #2985, #3295, #3297, #2512, #3302, #1222, #2631,
+ #3306, #3116, #3215, #1489, #3319, #3320, #3321, #1255, #2169, #3182, #3323,
+ #3301, #3318, #3029, #3338, #3346, #1144, #3173, #3165, #685, #3357, #2582,
+ #3370, #2438, #1253, #637, #1971, #2208, #979, #1790, #1888, #1882, #1793,
+ #754, #1890, #1931, #1246, #1025, #3177, #2496, #2567, #3400, #2213, #2027,
+ #3415, #1262, #3422, #2500, #3414, #3045, #3111, #2974, #2947, #3222, #2878,
+ #3402, #2909, #3423, #1328, #1852, #3382, #3393, #2029, #3489, #1853, #2026,
+ #2375, #3502, #3482, #3504, #3505, #3507, #2605, #3519, #3520, #3121, #3484,
+ #3439, #3216, #3511, #3524, #3521, #3197, #2486, #2449, #2748, #3381, #3236,
+ #671
+
+
+Conch 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The type of the protocols instantiated by SSHFactory is now parameterized
+ (#3443)
+
+Fixes
+-----
+ - A file descriptor leak has been fixed (#3213, #1789)
+ - "File Already Exists" errors are now handled more correctly (#3033)
+ - Handling of CR IAC in TelnetClient is now improved (#3305)
+ - SSHAgent is no longer completely unusable (#3332)
+ - The performance of insults.ClientProtocol is now greatly increased by
+ delivering more than one byte at a time to application code (#3386)
+ - Manhole and the conch server no longer need to be run as root when not
+ necessary (#2607)
+ - The value of FILEXFER_ATTR_ACMODTIME has been corrected (#2902)
+ - The management of known_hosts and host key verification has been overhauled
+ (#1376, #1301, #3494, #3496, #1292, #3499)
+
+Other
+-----
+ - #3193, #1633
+
+
+Lore 8.2.0 (2008-12-16)
+=======================
+
+Other
+-----
+ - #2207, #2514
+
+
+Mail 8.2.0 (2008-12-16)
+=======================
+
+Fixes
+-----
+ - The mailmail tool now provides better error messages for usage errors (#3339)
+ - The SMTP protocol implementation now works on PyPy (#2976)
+
+Other
+-----
+ - #3475
+
+
+Names 8.2.0 (2008-12-16)
+========================
+
+Features
+--------
+ - The NAPTR record type is now supported (#2276)
+
+Fixes
+-----
+ - Make client.Resolver less vulnerable to the Birthday Paradox attack by
+ avoiding sending duplicate queries when it's not necessary (#3347)
+ - client.Resolver now uses a random source port for each DNS request (#3342)
+ - client.Resolver now uses a full 16 bits of randomness for message IDs,
+ instead of 10 which it previously used (#3342)
+ - All record types now have value-based equality and a string representation
+ (#2935)
+
+Other
+-----
+ - #1622, #3424
+
+
+Web 8.2.0 (2008-12-16)
+======================
+
+Features
+--------
+ - The web server can now deal with multi-value headers in the new attributes of
+ Request, requestHeaders and responseHeaders (#165)
+ - There is now a resource-wrapper which implements HTTP Basic and Digest auth
+ in terms of twisted.cred (#696)
+ - It's now possible to limit the number of redirects that client.getPage will
+ follow (#2412)
+ - The directory-listing code no longer uses Woven (#3257)
+ - static.File now supports Range headers with a single range (#1493)
+ - twisted.web now has a rudimentary WSGI container (#2753)
+ - The web server now supports chunked encoding in requests (#3385)
+
+Fixes
+-----
+ - The xmlrpc client now raises an error when the server sends an empty
+ response (#3399)
+ - HTTPPageGetter no longer duplicates default headers when they're explicitly
+ overridden in the headers parameter (#1382)
+ - The server will no longer timeout clients which are still sending request
+ data (#1903)
+ - microdom's isEqualToNode now returns False when the nodes aren't equal
+ (#2542)
+
+Deprecations and Removals
+-------------------------
+
+ - Request.headers and Request.received_headers are not quite deprecated, but
+ they are discouraged in favor of requestHeaders and responseHeaders (#165)
+
+Other
+-----
+ - #909, #687, #2938, #1152, #2930, #2025, #2683, #3471
+
+
+Web2 8.2.0 (2008-12-16)
+=======================
+
+Note: Twisted Web2 is being phased out in preference for Twisted Web, but some
+maintenance changes have been made.
+
+Fixes
+-----
+ - The main twisted.web2 docstring now indicates the current state of the
+ project (#2028)
+ - Headers which require unusual bytes are now quoted (#2346)
+ - Some links in the introduction documentation have been fixed (#2552)
+
+
+Words 8.2.0 (2008-12-16)
+========================
+
+Feature
+-------
+ - There is now a standalone XMPP router included in twisted.words: it can be
+ used with the 'twistd xmpp-router' command line (#3407)
+ - A server factory for Jabber XML Streams has been added (#3435)
+ - Domish now allows for iterating child elements with specific qualified names
+ (#2429)
+ - IRCClient now has a 'back' method which removes the away status (#3366)
+ - IRCClient now has a 'whois' method (#3133)
+
+Fixes
+-----
+ - The IRC Client implementation can now deal with compound mode changes (#3230)
+ - The MSN protocol implementation no longer requires the CVR0 protocol to
+ be included in the VER command (#3394)
+ - In the IRC server implementation, topic messages will no longer be sent for
+ a group which has no topic (#2204)
+ - An infinite loop (which caused infinite memory usage) in irc.split has been
+ fixed. This was triggered any time a message that starts with a delimiter
+ was sent (#3446)
+ - Jabber's toResponse now generates a valid stanza even when stanzaType is not
+ specified (#3467)
+ - The lifetime of authenticator instances in XmlStreamServerFactory is no
+ longer artificially extended (#3464)
+
+Other
+-----
+ - #3365
+
+
+Core 8.1.0 (2008-05-18)
+=======================
+
+Features
+--------
+
+ - twisted.internet.error.ConnectionClosed is a new exception which is the
+ superclass of ConnectionLost and ConnectionDone (#3137)
+ - Trial's CPU and memory performance should be better now (#3034)
+ - twisted.python.filepath.FilePath now has a chmod method (#3124)
+
+Fixes
+-----
+
+ - Some reactor re-entrancy regressions were fixed (#3146, #3168)
+ - A regression was fixed whereby constructing a Failure for an exception and
+ traceback raised out of a Pyrex extension would fail (#3132)
+ - CopyableFailures in PB can again be created from CopiedFailures (#3174)
+ - FilePath.remove, when called on a FilePath representing a symlink to a
+ directory, no longer removes the contents of the targeted directory, and
+ instead removes the symlink (#3097)
+ - FilePath now has a linkTo method for creating new symlinks (#3122)
+ - The docstring for Trial's addCleanup method now correctly specifies when
+ cleanup functions are run (#3131)
+ - assertWarns now deals better with multiple identical warnings (#2904)
+ - Various windows installer bugs were fixed (#3115, #3144, #3150, #3151, #3164)
+ - API links in the howto documentation have been corrected (#3130)
+ - The Win32 Process transport object now has a pid attribute (#1836)
+ - A doc bug in the twistd plugin howto which would inevitably lead to
+ confusion was fixed (#3183)
+ - A regression breaking IOCP introduced after the last release was fixed
+ (#3200)
+
+Deprecations and Removals
+-------------------------
+
+ - mktap is now fully deprecated, and will emit DeprecationWarnings when used
+ (#3127)
+
+Other
+-----
+ - #3079, #3118, #3120, #3145, #3069, #3149, #3186, #3208, #2762
+
+
+Conch 8.1.0 (2008-05-18)
+========================
+
+Fixes
+-----
+ - A regression was fixed whereby the publicKeys and privateKeys attributes of
+ SSHFactory would not be interpreted as strings (#3141)
+ - The sshsimpleserver.py example had a minor bug fix (#3135)
+ - The deprecated mktap API is no longer used (#3127)
+ - An infelicity was fixed whereby a NameError would be raised in certain
+ circumstances during authentication when a ConchError should have been
+ (#3154)
+ - A workaround was added to conch.insults for a bug in gnome-terminal whereby
+ it would not scroll correctly (#3189)
+
+
+Lore 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+News 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Web 8.1.0 (2008-05-18)
+======================
+
+Fixes
+-----
+ - Fixed an XMLRPC bug whereby sometimes a callRemote Deferred would
+ accidentally be fired twice when a connection was lost during the handling of
+ a response (#3152)
+ - Fixed a bug in the "Using Twisted Web" document which prevented an example
+ resource from being renderable (#3147)
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Words 8.1.0 (2008-05-18)
+========================
+
+Features
+--------
+ - JID objects now have a nice __repr__ (#3156)
+ - Extending XMPP protocols is now easier (#2178)
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+ - A bug whereby one-time XMPP observers would be enabled permanently was fixed
+ (#3066)
+
+
+Mail 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Names 8.1.0 (2008-05-18)
+========================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Web2 8.1.0 (2008-05-18)
+=======================
+
+Fixes
+-----
+ - The deprecated mktap API is no longer used (#3127)
+
+
+Core 8.0.1 (2008-03-26)
+=======================
+
+Fixes
+-----
+ - README no longer refers to obsolete trial command line option
+ - twistd no longer causes a bizarre DeprecationWarning about mktap
+
+
+Core 8.0.0 (2008-03-17)
+=======================
+
+Features
+--------
+
+ - The IOCP reactor has had many changes and is now greatly improved
+ (#1760, #3055)
+ - The main Twisted distribution is now easy_installable (#1286, #3110)
+ - twistd can now profile with cProfile (#2469)
+ - twisted.internet.defer contains a DeferredFilesystemLock which gives a
+ Deferred interface to lock file acquisition (#2180)
+ - twisted.python.modules is a new system for representing and manipulating
+ module paths (i.e. sys.path) (#1951)
+ - twisted.internet.fdesc now contains a writeToFD function, along with other
+ minor fixes (#2419)
+ - twisted.python.usage now allows optional type enforcement (#739)
+ - The reactor now has a blockingCallFromThread method for non-reactor threads
+ to use to wait for a reactor-scheduled call to return a result (#1042, #3030)
+ - Exceptions raised inside of inlineCallbacks-using functions now have a
+ better chance of coming with a meaningful traceback (#2639, #2803)
+ - twisted.python.randbytes now contains code for generating secure random
+ bytes (#2685)
+ - The classes in twisted.application.internet now accept a reactor parameter
+ for specifying the reactor to use for underlying calls to allow for better
+ testability (#2937)
+ - LoopingCall now allows you to specify the reactor to use to schedule new
+ calls, allowing much better testing techniques (#2633, #2634)
+ - twisted.internet.task.deferLater is a new API for scheduling calls and
+ getting deferreds which are fired with their results (#1875)
+ - objgrep now knows how to search through deque objects (#2323)
+ - twisted.python.log now contains a Twisted log observer which can forward
+ messages to the Python logging system (#1351)
+ - Log files now include seconds in the timestamps (#867)
+ - It is now possible to limit the number of log files to create during log
+ rotation (#1095)
+ - The interface required by the log context system is now documented as
+ ILoggingContext, and abstract.FileDescriptor now declares that it implements
+ it (#1272)
+ - There is now an example cred checker that uses a database via adbapi (#460)
+ - The epoll reactor is now documented in the choosing-reactors howto (#2539)
+ - There were improvements to the client howto (#222)
+ - Int8Receiver was added (#2315)
+ - Various refactorings to AMP introduced better testability and public
+ interfaces (#2657, #2667, #2656, #2664, #2810)
+ - twisted.protocol.policies.TrafficLoggingFactory now has a resetCounter
+ method (#2757)
+ - The FTP client can be told which port range within which to bind passive
+ transfer ports (#1904)
+ - twisted.protocols.memcache contains a new asynchronous memcache client
+ (#2506, #2957)
+ - PB now supports anonymous login (#439, #2312)
+ - twisted.spread.jelly now supports decimal objects (#2920)
+ - twisted.spread.jelly now supports all forms of sets (#2958)
+ - There is now an interface describing the API that process protocols must
+ provide (#3020)
+ - Trial reporting to core unittest TestResult objects has been improved (#2495)
+ - Trial's TestCase now has an addCleanup method which allows easy setup of
+ tear-down code (#2610, #2899)
+ - Trial's TestCase now has an assertIsInstance method (#2749)
+ - Trial's memory footprint and speed are greatly improved (#2275)
+ - At the end of trial runs, "PASSED" and "FAILED" messages are now colorized
+ (#2856)
+ - Tests which leave global state around in the reactor will now fail in
+ trial. A new option, --unclean-warnings, will convert these errors back into
+ warnings (#2091)
+ - Trial now has a --without-module command line for testing code in an
+ environment that lacks a particular Python module (#1795)
+ - Error reporting of failed assertEquals assertions now has much nicer
+ formatting (#2893)
+ - Trial now has methods for monkey-patching (#2598)
+ - Trial now has an ITestCase (#2898, #1950)
+ - The trial reporter API now has a 'done' method which is called at the end of
+ a test run (#2883)
+ - TestCase now has an assertWarns method which allows testing that functions
+ emit warnings (#2626, #2703)
+ - There are now no string exceptions in the entire Twisted code base (#2063)
+ - There is now a system for specifying credentials checkers with a string
+ (#2570)
+
+Fixes
+-----
+
+ - Some tests which were asserting the value of stderr have been changed
+ because Python uncontrollably writes bytes to stderr (#2405)
+ - Log files handle time zones with DST better (#2404)
+ - Subprocesses using PTYs on OS X that are handled by Twisted will now be able
+ to more reliably write the final bytes before they exit, allowing Twisted
+ code to more reliably receive them (#2371, #2858)
+ - Trial unit test reporting has been improved (#1901)
+ - The kqueue reactor handles connection failures better (#2172)
+ - It's now possible to run "trial foo/bar/" without an exception: trailing
+ slashes no longer cause problems (#2005)
+ - cred portals now better deal with implementations of inherited interfaces
+ (#2523)
+ - FTP error handling has been improved (#1160, 1107)
+ - Trial behaves better with respect to file locking on Windows (#2482)
+ - The FTP server now gives a better error when STOR is attempted during an
+ anonymous session (#1575)
+ - Trial now behaves better with tests that use the reactor's threadpool (#1832)
+ - twisted.python.reload now behaves better with new-style objects (#2297)
+ - LogFile's defaultMode parameter is now better implemented, preventing
+ potential security exploits (#2586)
+ - A minor obscure leak in thread pools was corrected (#1134)
+ - twisted.internet.task.Clock now returns the correct DelayedCall from
+ callLater, instead of returning the one scheduled for the furthest in the
+ future (#2691)
+ - twisted.spread.util.FilePager no longer unnecessarily buffers data in
+ memory (#1843, 2321)
+ - Asking for twistd or trial to use an unavailable reactor no longer prints a
+ traceback (#2457)
+ - System event triggers have fewer obscure bugs (#2509)
+ - Plugin discovery code is much better behaved, allowing multiple
+ installations of a package with plugins (#2339, #2769)
+ - Process and PTYProcess have been merged and some minor bugs have been fixed
+ (#2341)
+ - The reactor has less global state (#2545)
+ - Failure can now correctly represent and format errors caused by string
+ exceptions (#2830)
+ - The epoll reactor now has better error handling which now avoids the bug
+ causing 100% CPU usage in some cases (#2809)
+ - Errors raised during trial setUp or tearDown methods are now handled better
+ (#2837)
+ - A problem when deferred callbacks add new callbacks to the deferred that
+ they are a callback of was fixed (#2849)
+ - Log messages that are emitted during connectionMade now have the protocol
+ prefix correctly set (#2813)
+ - The string representation of a TCP Server connection now contains the actual
+ port that it's bound to when it was configured to listen on port 0 (#2826)
+ - There is better reporting of error codes for TCP failures on Windows (#2425)
+ - Process spawning has been made slightly more robust by disabling garbage
+ collection temporarily immediately after forking so that finalizers cannot
+ be executed in an unexpected environment (#2483)
+ - namedAny now detects import errors better (#698)
+ - Many fixes and improvements to the twisted.python.zipstream module have
+ been made (#2996)
+ - FilePager no longer blows up on empty files (#3023)
+ - twisted.python.util.FancyEqMixin has been improved to cooperate with objects
+ of other types (#2944)
+ - twisted.python.FilePath.exists now restats to prevent incorrect result
+ (#2896)
+ - twisted.python.util.mergeFunctionMetadata now also merges the __module__
+ attribute (#3049)
+ - It is now possible to call transport.pauseProducing within connectionMade on
+ TCP transports without it being ignored (#1780)
+ - twisted.python.versions now understands new SVN metadata format for fetching
+ the SVN revision number (#3058)
+ - It's now possible to use reactor.callWhenRunning(reactor.stop) on gtk2 and
+ glib2 reactors (#3011)
+
+Deprecations and removals
+-------------------------
+ - twisted.python.timeoutqueue is now deprecated (#2536)
+ - twisted.enterprise.row and twisted.enterprise.reflector are now deprecated
+ (#2387)
+ - twisted.enterprise.util is now deprecated (#3022)
+ - The dispatch and dispatchWithCallback methods of ThreadPool are now
+ deprecated (#2684)
+ - Starting the same reactor multiple times is now deprecated (#1785)
+ - The visit method of various test classes in trial has been deprecated (#2897)
+ - The --report-profile option to twistd and twisted.python.dxprofile are
+ deprecated (#2908)
+ - The upDownError method of Trial reporters is deprecated (#2883)
+
+Other
+-----
+
+ - #2396, #2211, #1921, #2378, #2247, #1603, #2463, #2530, #2426, #2356, #2574,
+ - #1844, #2575, #2655, #2640, #2670, #2688, #2543, #2743, #2744, #2745, #2746,
+ - #2742, #2741, #1730, #2831, #2216, #1192, #2848, #2767, #1220, #2727, #2643,
+ - #2669, #2866, #2867, #1879, #2766, #2855, #2547, #2857, #2862, #1264, #2735,
+ - #942, #2885, #2739, #2901, #2928, #2954, #2906, #2925, #2942, #2894, #2793,
+ - #2761, #2977, #2968, #2895, #3000, #2990, #2919, #2969, #2921, #3005, #421,
+ - #3031, #2940, #1181, #2783, #1049, #3053, #2847, #2941, #2876, #2886, #3086,
+ - #3095, #3109
+
+
+Conch 8.0.0 (2008-03-17)
+========================
+
+Features
+--------
+ - Add DEC private mode manipulation methods to ITerminalTransport. (#2403)
+
+Fixes
+-----
+ - Parameterize the scheduler function used by the insults TopWindow widget.
+ This change breaks backwards compatibility in the TopWindow initializer.
+ (#2413)
+ - Notify subsystems, like SFTP, of connection close. (#2421)
+ - Change the process file descriptor "connection lost" code to reverse the
+ setNonBlocking operation done during initialization. (#2371)
+ - Change ConsoleManhole to wait for connectionLost notification before
+ stopping the reactor. (#2123, #2371)
+ - Make SSHUserAuthServer.ssh_USERAUTH_REQUEST return a Deferred. (#2528)
+ - Manhole's initializer calls its parent class's initializer with its
+ namespace argument. (#2587)
+ - Handle ^C during input line continuation in manhole by updating the prompt
+ and line buffer correctly. (#2663)
+ - Make twisted.conch.telnet.Telnet by default reject all attempts to enable
+ options. (#1967)
+ - Reduce the number of calls into application code to deliver application-level
+ data in twisted.conch.telnet.Telnet.dataReceived (#2107)
+ - Fix definition and management of extended attributes in conch file transfer.
+ (#3010)
+ - Fix parsing of OpenSSH-generated RSA keys with differing ASN.1 packing style.
+ (#3008)
+ - Fix handling of missing $HOME in twisted.conch.client.unix. (#3061)
+
+Misc
+----
+ - #2267, #2378, #2604, #2707, #2341, #2685, #2679, #2912, #2977, #2678, #2709
+ #2063, #2847
+
+
+Lore 8.0.0 (2008-03-17)
+=======================
+
+Fixes
+-----
+ - Change twisted.lore.tree.setIndexLin so that it removes node with index-link
+ class when the specified index filename is None. (#812)
+ - Fix the conversion of the list of options in man pages to Lore format.
+ (#3017)
+ - Fix conch man pages generation. (#3075)
+ - Fix management of the interactive command tag in man2lore. (#3076)
+
+Misc
+----
+ - #2847
+
+
+News 8.0.0 (2008-03-17)
+=======================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+Runner 8.0.0 (2008-03-17)
+=========================
+
+Misc
+----
+ - Remove all "API Stability" markers (#2847)
+
+
+Web 8.0.0 (2008-03-17)
+======================
+
+Features
+--------
+ - Add support to twisted.web.client.getPage for the HTTP HEAD method. (#2750)
+
+Fixes
+-----
+ - Set content-type in xmlrpc responses to "text/xml" (#2430)
+ - Add more error checking in the xmlrpc.XMLRPC render method, and enforce
+ POST requests. (#2505)
+ - Reject unicode input to twisted.web.client._parse to reject invalid
+ unicode URLs early. (#2628)
+ - Correctly re-quote URL path segments when generating an URL string to
+ return from Request.prePathURL. (#2934)
+ - Make twisted.web.proxy.ProxyClientFactory close the connection when
+ reporting a 501 error. (#1089)
+ - Fix twisted.web.proxy.ReverseProxyResource to specify the port in the
+ host header if different from 80. (#1117)
+ - Change twisted.web.proxy.ReverseProxyResource so that it correctly encodes
+ the request URI it sends on to the server for which it is a proxy. (#3013)
+ - Make "twistd web --personal" use PBServerFactory (#2681)
+
+Misc
+----
+ - #1996, #2382, #2211, #2633, #2634, #2640, #2752, #238, #2905
+
+
+Words 8.0.0 (2008-03-17)
+========================
+
+Features
+--------
+ - Provide function for creating XMPP response stanzas. (#2614, #2614)
+ - Log exceptions raised in Xish observers. (#2616)
+ - Add 'and' and 'or' operators for Xish XPath expressions. (#2502)
+ - Make JIDs hashable. (#2770)
+
+Fixes
+-----
+ - Respect the hostname and servername parameters to IRCClient.register. (#1649)
+ - Make EventDispatcher remove empty callback lists. (#1652)
+ - Use legacy base64 API to support Python 2.3 (#2461)
+ - Fix support of DIGEST-MD5 challenge parsing with multi-valued directives.
+ (#2606)
+ - Fix reuse of dict of prefixes in domish.Element.toXml (#2609)
+ - Properly process XMPP stream headers (#2615)
+ - Use proper namespace for XMPP stream errors. (#2630)
+ - Properly parse XMPP stream errors. (#2771)
+ - Fix toResponse for XMPP stanzas without an id attribute. (#2773)
+ - Move XMPP stream header procesing to authenticators. (#2772)
+
+Misc
+----
+ - #2617, #2640, #2741, #2063, #2570, #2847
+
+
+Mail 8.0.0 (2008-03-17)
+=======================
+
+Features
+--------
+ - Support CAPABILITY responses that include atoms of the form "FOO" and
+ "FOO=BAR" in IMAP4 (#2695)
+ - Parameterize error handling behavior of imap4.encoder and imap4.decoder.
+ (#2929)
+
+Fixes
+-----
+ - Handle empty passwords in SMTP auth. (#2521)
+ - Fix IMAP4Client's parsing of literals which are not preceeded by whitespace.
+ (#2700)
+ - Handle MX lookup suceeding without answers. (#2807)
+ - Fix issues with aliases(5) process support. (#2729)
+
+Misc
+----
+ - #2371, #2123, #2378, #739, #2640, #2746, #1917, #2266, #2864, #2832, #2063,
+ #2865, #2847
+
+
+Names 8.0.0 (2008-03-17)
+========================
+
+Fixes
+-----
+
+ - Refactor DNSDatagramProtocol and DNSProtocol to use same base class (#2414)
+ - Change Resolver to query specified nameservers in specified order, instead
+ of reverse order. (#2290)
+ - Make SRVConnector work with bad results and NXDOMAIN responses.
+ (#1908, #2777)
+ - Handle write errors happening in dns queries, to have correct deferred
+ failures. (#2492)
+ - Fix the value of OP_NOTIFY and add a definition for OP_UPDATE. (#2945)
+
+Misc
+----
+ - #2685, #2936, #2581, #2847
+
diff --git a/README b/README
new file mode 100644
index 0000000..c0134b8
--- /dev/null
+++ b/README
@@ -0,0 +1,116 @@
+Twisted 12.1.0
+
+Quote of the Release:
+
+
+ efnet is the cutting edge of hanging out with people on the internet in the '90s
+
+
+For information on what's new in Twisted 12.1.0, see the NEWS file that comes
+with the distribution.
+
+What is this?
+=============
+
+ Twisted is an event-based framework for internet applications. It includes
+ modules for many different purposes, including the following:
+
+ - twisted.application
+ A "Service" system that allows you to organize your application in
+ hierarchies with well-defined startup and dependency semantics,
+ - twisted.cred
+ A general credentials and authentication system that facilitates
+ pluggable authentication backends,
+ - twisted.enterprise
+ Asynchronous database access, compatible with any Python DBAPI2.0
+ modules,
+ - twisted.internet
+ Low-level asynchronous networking APIs that allow you to define
+ your own protocols that run over certain transports,
+ - twisted.manhole
+ A tool for remote debugging of your services which gives you a
+ Python interactive interpreter,
+ - twisted.protocols
+ Basic protocol implementations and helpers for your own protocol
+ implementations,
+ - twisted.python
+ A large set of utilities for Python tricks, reflection, text
+ processing, and anything else,
+ - twisted.spread
+ A secure, fast remote object system,
+ - twisted.trial
+ A unit testing framework that integrates well with Twisted-based code.
+
+ Twisted supports integration of the Win32, Tk, GTK+ and GTK+ 2 event loops
+ with its main event loop. There is experimental support for Mac OS X and
+ wxPython event loop integration, which you use at your peril.
+
+ For more information, visit http://www.twistedmatrix.com, or join the list
+ at http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
+
+ There are many official Twisted subprojects, including clients and
+ servers for web, mail, DNS, and more. You can find out more about
+ these projects at http://twistedmatrix.com/trac/wiki/TwistedProjects
+
+
+Installing
+==========
+
+ Instructions for installing this software are in INSTALL.
+
+Unit Tests
+==========
+
+
+ See our unit tests run proving that the software is BugFree(TM):
+
+ % trial twisted
+
+ Some of these tests may fail if you
+ * don't have the dependancies required for a particular subsystem installed,
+ * have a firewall blocking some ports (or things like Multicast, which Linux
+ NAT has shown itself to do), or
+ * run them as root.
+
+
+Documentation and Support
+=========================
+
+ Examples on how to use Twisted APIs are located in doc/core/examples; this
+ might ease the learning curve a little bit, since all these files are kept
+ as short as possible. The file doc/core/howto/index.xhtml contains an index
+ of all the core HOWTOs: this should be your starting point when looking for
+ documentation.
+
+ Help is available on the Twisted mailing list:
+
+ http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python
+
+ There is also a very lively IRC channel, #twisted, on
+ chat.freenode.net.
+
+
+Copyright
+=========
+
+ All of the code in this distribution is Copyright (c) 2001-2012
+ Twisted Matrix Laboratories.
+
+ Twisted is made available under the MIT license. The included
+ LICENSE file describes this in detail.
+
+
+Warranty
+========
+
+ THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+ EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+ TO THE USE OF THIS SOFTWARE IS WITH YOU.
+
+ IN NO EVENT WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+ AND/OR REDISTRIBUTE THE LIBRARY, BE LIABLE TO YOU FOR ANY DAMAGES, EVEN IF
+ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGES.
+
+ Again, see the included LICENSE file for specific legal details.
diff --git a/bin/_preamble.py b/bin/_preamble.py
new file mode 100644
index 0000000..fcd6014
--- /dev/null
+++ b/bin/_preamble.py
@@ -0,0 +1,19 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# This makes sure that users don't have to set up their environment
+# specially in order to run these programs from bin/.
+
+# This helper is shared by many different actual scripts. It is not intended to
+# be packaged or installed, it is only a developer convenience. By the time
+# Twisted is actually installed somewhere, the environment should already be set
+# up properly without the help of this tool.
+
+import sys, os
+
+path = os.path.abspath(sys.argv[0])
+while os.path.dirname(path) != path:
+ if os.path.exists(os.path.join(path, 'twisted', '__init__.py')):
+ sys.path.insert(0, path)
+ break
+ path = os.path.dirname(path)
diff --git a/bin/conch/cftp b/bin/conch/cftp
new file mode 100755
index 0000000..106fa0b
--- /dev/null
+++ b/bin/conch/cftp
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.conch.scripts.cftp import run
+run()
diff --git a/bin/conch/ckeygen b/bin/conch/ckeygen
new file mode 100755
index 0000000..df31973
--- /dev/null
+++ b/bin/conch/ckeygen
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.conch.scripts.ckeygen import run
+run()
diff --git a/bin/conch/conch b/bin/conch/conch
new file mode 100755
index 0000000..8ad4b99
--- /dev/null
+++ b/bin/conch/conch
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.conch.scripts.conch import run
+run()
diff --git a/bin/conch/tkconch b/bin/conch/tkconch
new file mode 100755
index 0000000..78376a3
--- /dev/null
+++ b/bin/conch/tkconch
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.conch.scripts.tkconch import run
+run()
diff --git a/bin/lore/lore b/bin/lore/lore
new file mode 100755
index 0000000..6515828
--- /dev/null
+++ b/bin/lore/lore
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.lore.scripts.lore import run
+run()
+
diff --git a/bin/mail/mailmail b/bin/mail/mailmail
new file mode 100755
index 0000000..d4bfb3c
--- /dev/null
+++ b/bin/mail/mailmail
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This script attempts to send some email.
+"""
+
+import sys, os
+extra = os.path.dirname(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, extra)
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+sys.path.remove(extra)
+
+from twisted.mail.scripts import mailmail
+mailmail.run()
+
diff --git a/bin/manhole b/bin/manhole
new file mode 100755
index 0000000..35f78ff
--- /dev/null
+++ b/bin/manhole
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This script runs GtkManhole, a client for Twisted.Manhole
+"""
+import sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+from twisted.scripts import manhole
+manhole.run()
diff --git a/bin/pyhtmlizer b/bin/pyhtmlizer
new file mode 100755
index 0000000..f212b10
--- /dev/null
+++ b/bin/pyhtmlizer
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+import sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+from twisted.scripts.htmlizer import run
+run()
diff --git a/bin/tap2deb b/bin/tap2deb
new file mode 100755
index 0000000..73d2032
--- /dev/null
+++ b/bin/tap2deb
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+tap2deb
+"""
+import sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+from twisted.scripts import tap2deb
+tap2deb.run()
diff --git a/bin/tap2rpm b/bin/tap2rpm
new file mode 100755
index 0000000..c2f7368
--- /dev/null
+++ b/bin/tap2rpm
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# based off the tap2deb code
+# tap2rpm built by Sean Reifschneider,
+
+"""
+tap2rpm
+"""
+import sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+from twisted.scripts import tap2rpm
+tap2rpm.run()
diff --git a/bin/tapconvert b/bin/tapconvert
new file mode 100755
index 0000000..127dc0b
--- /dev/null
+++ b/bin/tapconvert
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+import sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+from twisted.scripts.tapconvert import run
+run()
diff --git a/bin/trial b/bin/trial
new file mode 100755
index 0000000..c45a1ca
--- /dev/null
+++ b/bin/trial
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+import os, sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+# begin chdir armor
+sys.path[:] = map(os.path.abspath, sys.path)
+# end chdir armor
+
+sys.path.insert(0, os.path.abspath(os.getcwd()))
+
+from twisted.scripts.trial import run
+run()
diff --git a/bin/twistd b/bin/twistd
new file mode 100755
index 0000000..6035e5f
--- /dev/null
+++ b/bin/twistd
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+import os, sys
+
+try:
+ import _preamble
+except ImportError:
+ sys.exc_clear()
+
+sys.path.insert(0, os.path.abspath(os.getcwd()))
+
+from twisted.scripts.twistd import run
+run()
diff --git a/doc/conch/benchmarks/README b/doc/conch/benchmarks/README
new file mode 100644
index 0000000..233bc8e
--- /dev/null
+++ b/doc/conch/benchmarks/README
@@ -0,0 +1,15 @@
+This directory contains various simple programs intended to exercise various
+features of Twisted Conch as a way to learn about and track their
+performance characteristics. As there is currently no record of past
+benchmark results, the tracking aspect of this is currently somewhat
+fantastic. However, the intent is for this to change at some future point.
+
+All (one) of the programs in this directory are currently intended to be
+invoked directly and to report some timing information on standard out.
+
+The following benchmarks are currently available:
+
+buffering_mixin.py:
+
+ This deals with twisted.conch.mixin.BufferingMixin which provides
+ Nagle-like write coalescing for Protocol classes.
diff --git a/doc/conch/benchmarks/buffering_mixin.py b/doc/conch/benchmarks/buffering_mixin.py
new file mode 100755
index 0000000..7009df8
--- /dev/null
+++ b/doc/conch/benchmarks/buffering_mixin.py
@@ -0,0 +1,182 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Benchmarks comparing the write performance of a "normal" Protocol instance
+and an instance of a Protocol class which has had L{twisted.conch.mixin}'s
+L{BufferingMixin} mixed in to perform
+Nagle-like write coalescing.
+"""
+
+from sys import stdout
+from pprint import pprint
+from time import time
+
+from twisted.python.usage import Options
+from twisted.python.log import startLogging
+
+from twisted.internet.protocol import ServerFactory, Protocol, ClientCreator
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+
+from twisted.conch.mixin import BufferingMixin
+
+
+class BufferingBenchmark(Options):
+ """
+ Options for configuring the execution parameters of a benchmark run.
+ """
+
+ optParameters = [
+ ('scale', 's', '1',
+ 'Work multiplier (bigger takes longer, might resist noise better)')]
+
+ def postOptions(self):
+ self['scale'] = int(self['scale'])
+
+
+
+class ServerProtocol(Protocol):
+ """
+ A silent protocol which only waits for a particular amount of input and
+ then fires a Deferred.
+ """
+ def __init__(self, expected, finished):
+ self.expected = expected
+ self.finished = finished
+
+
+ def dataReceived(self, bytes):
+ self.expected -= len(bytes)
+ if self.expected == 0:
+ finished, self.finished = self.finished, None
+ finished.callback(None)
+
+
+
+class BufferingProtocol(Protocol, BufferingMixin):
+ """
+ A protocol which uses the buffering mixin to provide a write method.
+ """
+
+
+
+class UnbufferingProtocol(Protocol):
+ """
+ A protocol which provides a naive write method which simply passes through
+ to the transport.
+ """
+
+ def connectionMade(self):
+ """
+ Bind write to the transport's write method and flush to a no-op
+ function in order to provide the same API as is provided by
+ BufferingProtocol.
+ """
+ self.write = self.transport.write
+ self.flush = lambda: None
+
+
+
+def _write(proto, byteCount):
+ write = proto.write
+ flush = proto.flush
+
+ for i in range(byteCount):
+ write('x')
+ flush()
+
+
+
+def _benchmark(byteCount, clientProtocol):
+ result = {}
+ finished = Deferred()
+ def cbFinished(ignored):
+ result[u'disconnected'] = time()
+ result[u'duration'] = result[u'disconnected'] - result[u'connected']
+ return result
+ finished.addCallback(cbFinished)
+
+ f = ServerFactory()
+ f.protocol = lambda: ServerProtocol(byteCount, finished)
+ server = reactor.listenTCP(0, f)
+
+ f2 = ClientCreator(reactor, clientProtocol)
+ proto = f2.connectTCP('127.0.0.1', server.getHost().port)
+ def connected(proto):
+ result[u'connected'] = time()
+ return proto
+ proto.addCallback(connected)
+ proto.addCallback(_write, byteCount)
+ return finished
+
+
+
+def _benchmarkBuffered(byteCount):
+ return _benchmark(byteCount, BufferingProtocol)
+
+
+
+def _benchmarkUnbuffered(byteCount):
+ return _benchmark(byteCount, UnbufferingProtocol)
+
+
+
+def benchmark(scale=1):
+ """
+ Benchmark and return information regarding the relative performance of a
+ protocol which does not use the buffering mixin and a protocol which
+ does.
+
+ @type scale: C{int}
+ @param scale: A multipler to the amount of work to perform
+
+ @return: A Deferred which will fire with a dictionary mapping each of
+ the two unicode strings C{u'buffered'} and C{u'unbuffered'} to
+ dictionaries describing the performance of a protocol of each type.
+ These value dictionaries will map the unicode strings C{u'connected'}
+ and C{u'disconnected'} to the times at which each of those events
+ occurred and C{u'duration'} two the difference between these two values.
+ """
+ overallResult = {}
+
+ byteCount = 1024
+
+ bufferedDeferred = _benchmarkBuffered(byteCount * scale)
+ def didBuffered(bufferedResult):
+ overallResult[u'buffered'] = bufferedResult
+ unbufferedDeferred = _benchmarkUnbuffered(byteCount * scale)
+ def didUnbuffered(unbufferedResult):
+ overallResult[u'unbuffered'] = unbufferedResult
+ return overallResult
+ unbufferedDeferred.addCallback(didUnbuffered)
+ return unbufferedDeferred
+ bufferedDeferred.addCallback(didBuffered)
+ return bufferedDeferred
+
+
+
+def main(args=None):
+ """
+ Perform a single benchmark run, starting and stopping the reactor and
+ logging system as necessary.
+ """
+ startLogging(stdout)
+
+ options = BufferingBenchmark()
+ options.parseOptions(args)
+
+ d = benchmark(options['scale'])
+ def cbBenchmark(result):
+ pprint(result)
+ def ebBenchmark(err):
+ print err.getTraceback()
+ d.addCallbacks(cbBenchmark, ebBenchmark)
+ def stopReactor(ign):
+ reactor.stop()
+ d.addBoth(stopReactor)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/conch/examples/demo.tac b/doc/conch/examples/demo.tac
new file mode 100644
index 0000000..8eb492c
--- /dev/null
+++ b/doc/conch/examples/demo.tac
@@ -0,0 +1,25 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo.tac
+
+"""Nearly pointless demonstration of the manhole interactive interpreter.
+
+This does about the same thing as demo_manhole, but uses the tap
+module's makeService method instead. The only interesting difference
+is that in this version, the telnet server also requires
+authentication.
+
+Note, you will have to create a file named \"passwd\" and populate it
+with credentials (in the format of passwd(5)) to use this demo.
+"""
+
+from twisted.application import service
+application = service.Application("TAC Demo")
+
+from twisted.conch import manhole_tap
+manhole_tap.makeService({"telnetPort": "tcp:6023",
+ "sshPort": "tcp:6022",
+ "namespace": {"foo": "bar"},
+ "passwd": "passwd"}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_draw.tac b/doc/conch/examples/demo_draw.tac
new file mode 100644
index 0000000..d80b064
--- /dev/null
+++ b/doc/conch/examples/demo_draw.tac
@@ -0,0 +1,80 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_draw.tac
+
+"""A trivial drawing application.
+
+Clients are allowed to connect and spew various characters out over
+the terminal. Spacebar changes the drawing character, while the arrow
+keys move the cursor.
+"""
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class Draw(insults.TerminalProtocol):
+ """Protocol which accepts arrow key and spacebar input and places
+ the requested characters onto the terminal.
+ """
+ cursors = list('!@#$%^&*()_+-=')
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetModes([insults.modes.IRM])
+ self.cursor = self.cursors[0]
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == self.terminal.UP_ARROW:
+ self.terminal.cursorUp()
+ elif keyID == self.terminal.DOWN_ARROW:
+ self.terminal.cursorDown()
+ elif keyID == self.terminal.LEFT_ARROW:
+ self.terminal.cursorBackward()
+ elif keyID == self.terminal.RIGHT_ARROW:
+ self.terminal.cursorForward()
+ elif keyID == ' ':
+ self.cursor = self.cursors[(self.cursors.index(self.cursor) + 1) % len(self.cursors)]
+ else:
+ return
+ self.terminal.write(self.cursor)
+ self.terminal.cursorBackward()
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+makeService({'protocolFactory': Draw,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_insults.tac b/doc/conch/examples/demo_insults.tac
new file mode 100644
index 0000000..ebb01c5
--- /dev/null
+++ b/doc/conch/examples/demo_insults.tac
@@ -0,0 +1,252 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_insults.tac
+
+"""Various simple terminal manipulations using the insults module.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here ignores most user input
+(except to print it out to the server log) and spends the duration of
+the connection drawing (the author's humble approximation of)
+raindrops at random locations on the client's terminal. +, -, *, and
+/ are respected and each adjusts an aspect of the timing of the
+animation process.
+"""
+
+import random, string
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DrawingFinished(Exception):
+ """Sentinel exception, raised when no \"frames\" for a particular
+ \"animation\" remain to be drawn.
+ """
+
+class Drawable:
+ """Representation of an animation.
+
+ Constructed with a protocol instance and a coordinate on the
+ screen, waits for invocations of iterate() at which point it
+ erases the previous frame of the animation and draws the next one,
+ using its protocol instance and always placing the upper left hand
+ corner of the frame at the given coordinates.
+
+ Frames are defined with draw_ prefixed methods. Erasure is
+ performed by erase_ prefixed methods.
+ """
+ n = 0
+
+ def __init__(self, proto, col, line):
+ self.proto = proto
+ self.col = col
+ self.line = line
+
+ def drawLines(self, s):
+ lines = s.splitlines()
+ c = self.col
+ line = self.line
+ for l in lines:
+ self.proto.cursorPosition(c - len(lines) / 2, line)
+ self.proto.write(l)
+ line += 1
+
+ def iterate(self):
+ getattr(self, 'erase_' + str(self.n))()
+ self.n += 1
+ f = getattr(self, 'draw_' + str(self.n), None)
+ if f is None:
+ raise DrawingFinished()
+ f()
+
+ def erase_0(self):
+ pass
+
+
+class Splat(Drawable):
+ HEIGHT = 5
+ WIDTH = 11
+
+ def draw_1(self):
+ # . .
+ #. . .
+ # . .
+ self.drawLines(' . .\n. . .\n . .')
+
+ def erase_1(self):
+ self.drawLines(' \n \n ')
+
+ def draw_2(self):
+ # . . . .
+ # . o o o .
+ #. o o o o .
+ # . o o o .
+ # . . . .
+ self.drawLines(' . . . .\n . o o o .\n. o o o o .\n . o o o .\n . . . .')
+
+ def erase_2(self):
+ self.drawLines(' \n \n \n \n ')
+
+ def draw_3(self):
+ # o o o o
+ # o O O O o
+ #o O O O O o
+ # o O O O o
+ # o o o o
+ self.drawLines(' o o o o\n o O O O o\no O O O O o\n o O O O o\n o o o o')
+
+ erase_3 = erase_2
+
+ def draw_4(self):
+ # O O O O
+ # O . . . O
+ #O . . . . O
+ # O . . . O
+ # O O O O
+ self.drawLines(' O O O O\n O . . . O\nO . . . . O\n O . . . O\n O O O O')
+
+ erase_4 = erase_3
+
+ def draw_5(self):
+ # . . . .
+ # . .
+ #. .
+ # . .
+ # . . . .
+ self.drawLines(' . . . .\n . .\n. .\n . .\n . . . .')
+
+ erase_5 = erase_4
+
+class Drop(Drawable):
+ WIDTH = 3
+ HEIGHT = 4
+
+ def draw_1(self):
+ # o
+ self.drawLines(' o')
+
+ def erase_1(self):
+ self.drawLines(' ')
+
+ def draw_2(self):
+ # _
+ #/ \
+ #\./
+ self.drawLines(' _ \n/ \\\n\\./')
+
+ def erase_2(self):
+ self.drawLines(' \n \n ')
+
+ def draw_3(self):
+ # O
+ self.drawLines(' O')
+
+ def erase_3(self):
+ self.drawLines(' ')
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Draws random things at random places on the screen.
+ """
+ width = 80
+ height = 24
+
+ interval = 0.1
+ rate = 0.05
+
+ def connectionMade(self):
+ self.run()
+
+ def connectionLost(self, reason):
+ self._call.stop()
+ del self._call
+
+ def run(self):
+ # Clear the screen, matey
+ self.terminal.eraseDisplay()
+
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+ def _iterate(self):
+ cls = random.choice((Splat, Drop))
+
+ # Move to a random location on the screen
+ col = random.randrange(self.width - cls.WIDTH) + cls.WIDTH
+ line = random.randrange(self.height - cls.HEIGHT) + cls.HEIGHT
+
+ s = cls(self.terminal, col, line)
+
+ c = task.LoopingCall(s.iterate)
+ c.start(self.rate).addErrback(lambda f: f.trap(DrawingFinished)).addErrback(log.err)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '+':
+ self.interval /= 1.1
+ elif keyID == '-':
+ self.interval *= 1.1
+ elif keyID == '*':
+ self.rate /= 1.1
+ elif keyID == '/':
+ self.rate *= 1.1
+ else:
+ log.msg("Client sent: %r" % (keyID,))
+ return
+
+ self._call.stop()
+ self._call = task.LoopingCall(self._iterate)
+ self._call.start(self.interval)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_manhole.tac b/doc/conch/examples/demo_manhole.tac
new file mode 100644
index 0000000..11808c6
--- /dev/null
+++ b/doc/conch/examples/demo_manhole.tac
@@ -0,0 +1,56 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_manhole.tac
+
+"""An interactive Python interpreter with syntax coloring.
+
+Nothing interesting is actually defined here. Two listening ports are
+set up and attached to protocols which know how to properly set up a
+ColoredManhole instance.
+"""
+
+from twisted.conch.manhole import ColoredManhole
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Interactive Python Interpreter")
+
+makeService({'protocolFactory': ColoredManhole,
+ 'protocolArgs': (None,),
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_recvline.tac b/doc/conch/examples/demo_recvline.tac
new file mode 100644
index 0000000..17ec78b
--- /dev/null
+++ b/doc/conch/examples/demo_recvline.tac
@@ -0,0 +1,77 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_recvline.tac
+
+"""Demonstrates line-at-a-time handling with basic line-editing support.
+
+This is a variation on the echo server. It sets up two listening
+ports: one on 6022 which accepts ssh connections; one on 6023 which
+accepts telnet connections. No login for the telnet server is
+required; for the ssh server, \"username\" is the username and
+\"password\" is the password.
+
+The demo protocol defined in this module is handed a line of input at
+a time, which it simply writes back to the connection.
+HistoricRecvline, which the demo protocol subclasses, provides basic
+line editing and input history features.
+"""
+
+from twisted.conch import recvline
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+class DemoRecvLine(recvline.HistoricRecvLine):
+ """Simple echo protocol.
+
+ Accepts lines of input and writes them back to its connection. If
+ a line consisting solely of \"quit\" is received, the connection
+ is dropped.
+ """
+
+ def lineReceived(self, line):
+ if line == "quit":
+ self.terminal.loseConnection()
+ self.terminal.write(line)
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Insults RecvLine Demo")
+
+makeService({'protocolFactory': DemoRecvLine,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/demo_scroll.tac b/doc/conch/examples/demo_scroll.tac
new file mode 100644
index 0000000..3e63c09
--- /dev/null
+++ b/doc/conch/examples/demo_scroll.tac
@@ -0,0 +1,100 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# You can run this .tac file directly with:
+# twistd -ny demo_scroll.tac
+
+"""Simple echo-ish server that uses the scroll-region.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login
+for the telnet server is required; for the ssh server, \"username\" is
+the username and \"password\" is the password.
+
+The TerminalProtocol subclass defined here sets up a scroll-region occupying
+most of the screen. It positions the cursor at the bottom of the screen and
+then echos back printable input. When return is received, the line is
+copied to the upper area of the screen (scrolling anything older up) and
+clears the input line.
+"""
+
+import string
+
+from twisted.python import log
+from twisted.internet import protocol
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+class DemoProtocol(insults.TerminalProtocol):
+ """Copies input to an upwards scrolling region.
+ """
+ width = 80
+ height = 24
+
+ def connectionMade(self):
+ self.buffer = []
+ self.terminalSize(self.width, self.height)
+
+ # ITerminalListener
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+
+ self.terminal.setScrollRegion(0, height - 1)
+ self.terminal.cursorPosition(0, height)
+ self.terminal.write('> ')
+
+ def unhandledControlSequence(self, seq):
+ log.msg("Client sent something weird: %r" % (seq,))
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r':
+ self.terminal.cursorPosition(0, self.height - 2)
+ self.terminal.nextLine()
+ self.terminal.write(''.join(self.buffer))
+ self.terminal.cursorPosition(0, self.height - 1)
+ self.terminal.eraseToLineEnd()
+ self.terminal.write('> ')
+ self.buffer = []
+ elif keyID in list(string.printable):
+ self.terminal.write(keyID)
+ self.buffer.append(keyID)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Scroll Region Demo App")
+
+makeService({'protocolFactory': DemoProtocol,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/examples/index.html b/doc/conch/examples/index.html
new file mode 100644
index 0000000..cdb7d5e
--- /dev/null
+++ b/doc/conch/examples/index.html
@@ -0,0 +1,40 @@
+
+
+Twisted Documentation: Twisted Conch code examples
+
+
+
+
+ Twisted Conch code examples
+
+
+
+
+
Simple SSH server and client
+
+
+
Simple telnet server
+
+ telnet_echo.tac - A telnet server which echoes data and events back to the client
+
+
+
+
twisted.conch.insults examples
+
+ demo.tac - Nearly pointless demonstration of the manhole interactive interpreter
+ demo_draw.tac - A trivial drawing application
+ demo_insults.tac - Various simple terminal manipulations using the insults module
+ demo_recvline.tac - Demonstrates line-at-a-time handling with basic line-editing support
+ demo_scroll.tac - Simple echo-ish server that uses the scroll-region
+ demo_manhole.tac - An interactive Python interpreter with syntax coloring
+ window.tac - An example of various widgets
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/examples/sshsimpleclient.py b/doc/conch/examples/sshsimpleclient.py
new file mode 100644
index 0000000..0f7738a
--- /dev/null
+++ b/doc/conch/examples/sshsimpleclient.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.conch.ssh import transport, userauth, connection, common, keys, channel
+from twisted.internet import defer, protocol, reactor
+from twisted.python import log
+import struct, sys, getpass, os
+
+USER = 'z3p' # replace this with a valid username
+HOST = 'localhost' # and a valid host
+
+class SimpleTransport(transport.SSHClientTransport):
+ def verifyHostKey(self, hostKey, fingerprint):
+ print 'host key fingerprint: %s' % fingerprint
+ return defer.succeed(1)
+
+ def connectionSecure(self):
+ self.requestService(
+ SimpleUserAuth(USER,
+ SimpleConnection()))
+
+class SimpleUserAuth(userauth.SSHUserAuthClient):
+ def getPassword(self):
+ return defer.succeed(getpass.getpass("%s@%s's password: " % (USER, HOST)))
+
+ def getGenericAnswers(self, name, instruction, questions):
+ print name
+ print instruction
+ answers = []
+ for prompt, echo in questions:
+ if echo:
+ answer = raw_input(prompt)
+ else:
+ answer = getpass.getpass(prompt)
+ answers.append(answer)
+ return defer.succeed(answers)
+
+ def getPublicKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ # this works with rsa too
+ # just change the name here and in getPrivateKey
+ if not os.path.exists(path) or self.lastPublicKey:
+ # the file doesn't exist, or we've tried a public key
+ return
+ return keys.Key.fromFile(filename=path+'.pub').blob()
+
+ def getPrivateKey(self):
+ path = os.path.expanduser('~/.ssh/id_dsa')
+ return defer.succeed(keys.Key.fromFile(path).keyObject)
+
+class SimpleConnection(connection.SSHConnection):
+ def serviceStarted(self):
+ self.openChannel(TrueChannel(2**16, 2**15, self))
+ self.openChannel(FalseChannel(2**16, 2**15, self))
+ self.openChannel(CatChannel(2**16, 2**15, self))
+
+class TrueChannel(channel.SSHChannel):
+ name = 'session' # needed for commands
+
+ def openFailed(self, reason):
+ print 'true failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('true'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'true status was: %s' % status
+ self.loseConnection()
+
+class FalseChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'false failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.conn.sendRequest(self, 'exec', common.NS('false'))
+
+ def request_exit_status(self, data):
+ status = struct.unpack('>L', data)[0]
+ print 'false status was: %s' % status
+ self.loseConnection()
+
+class CatChannel(channel.SSHChannel):
+ name = 'session'
+
+ def openFailed(self, reason):
+ print 'echo failed', reason
+
+ def channelOpen(self, ignoredData):
+ self.data = ''
+ d = self.conn.sendRequest(self, 'exec', common.NS('cat'), wantReply = 1)
+ d.addCallback(self._cbRequest)
+
+ def _cbRequest(self, ignored):
+ self.write('hello conch\n')
+ self.conn.sendEOF(self)
+
+ def dataReceived(self, data):
+ self.data += data
+
+ def closed(self):
+ print 'got data from cat: %s' % repr(self.data)
+ self.loseConnection()
+ reactor.stop()
+
+protocol.ClientCreator(reactor, SimpleTransport).connectTCP(HOST, 22)
+reactor.run()
diff --git a/doc/conch/examples/sshsimpleserver.py b/doc/conch/examples/sshsimpleserver.py
new file mode 100755
index 0000000..5ebcee8
--- /dev/null
+++ b/doc/conch/examples/sshsimpleserver.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.cred import portal, checkers
+from twisted.conch import error, avatar
+from twisted.conch.checkers import SSHPublicKeyDatabase
+from twisted.conch.ssh import factory, userauth, connection, keys, session
+from twisted.internet import reactor, protocol, defer
+from twisted.python import log
+from zope.interface import implements
+import sys
+log.startLogging(sys.stderr)
+
+"""
+Example of running another protocol over an SSH channel.
+log in with username "user" and password "password".
+"""
+
+class ExampleAvatar(avatar.ConchUser):
+
+ def __init__(self, username):
+ avatar.ConchUser.__init__(self)
+ self.username = username
+ self.channelLookup.update({'session':session.SSHSession})
+
+class ExampleRealm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ return interfaces[0], ExampleAvatar(avatarId), lambda: None
+
+class EchoProtocol(protocol.Protocol):
+ """this is our example protocol that we will run over SSH
+ """
+ def dataReceived(self, data):
+ if data == '\r':
+ data = '\r\n'
+ elif data == '\x03': #^C
+ self.transport.loseConnection()
+ return
+ self.transport.write(data)
+
+publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHRivcJSkbh/C+BR3utDS555mV'
+
+privateKey = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+
+
+class InMemoryPublicKeyChecker(SSHPublicKeyDatabase):
+
+ def checkKey(self, credentials):
+ return credentials.username == 'user' and \
+ keys.Key.fromString(data=publicKey).blob() == credentials.blob
+
+class ExampleSession:
+
+ def __init__(self, avatar):
+ """
+ We don't use it, but the adapter is passed the avatar as its first
+ argument.
+ """
+
+ def getPty(self, term, windowSize, attrs):
+ pass
+
+ def execCommand(self, proto, cmd):
+ raise Exception("no executing commands")
+
+ def openShell(self, trans):
+ ep = EchoProtocol()
+ ep.makeConnection(trans)
+ trans.makeConnection(session.wrapProtocol(ep))
+
+ def eofReceived(self):
+ pass
+
+ def closed(self):
+ pass
+
+from twisted.python import components
+components.registerAdapter(ExampleSession, ExampleAvatar, session.ISession)
+
+class ExampleFactory(factory.SSHFactory):
+ publicKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=publicKey)
+ }
+ privateKeys = {
+ 'ssh-rsa': keys.Key.fromString(data=privateKey)
+ }
+ services = {
+ 'ssh-userauth': userauth.SSHUserAuthServer,
+ 'ssh-connection': connection.SSHConnection
+ }
+
+
+portal = portal.Portal(ExampleRealm())
+passwdDB = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+passwdDB.addUser('user', 'password')
+portal.registerChecker(passwdDB)
+portal.registerChecker(InMemoryPublicKeyChecker())
+ExampleFactory.portal = portal
+
+if __name__ == '__main__':
+ reactor.listenTCP(5022, ExampleFactory())
+ reactor.run()
diff --git a/doc/conch/examples/telnet_echo.tac b/doc/conch/examples/telnet_echo.tac
new file mode 100644
index 0000000..daa5b38
--- /dev/null
+++ b/doc/conch/examples/telnet_echo.tac
@@ -0,0 +1,47 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple echo server that echoes back client input.
+
+You can run this .tac file directly with:
+ twistd -ny telnet_echo.tac
+
+This demo sets up a listening port on 6023 which accepts telnet connections.
+No login for the telnet server is required.
+"""
+
+from twisted.conch.telnet import TelnetTransport, TelnetProtocol
+from twisted.internet.protocol import ServerFactory
+from twisted.application.internet import TCPServer
+from twisted.application.service import Application
+
+class TelnetEcho(TelnetProtocol):
+ def enableRemote(self, option):
+ self.transport.write("You tried to enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableRemote(self, option):
+ self.transport.write("You disabled %r\r\n" % (option,))
+
+
+ def enableLocal(self, option):
+ self.transport.write("You tried to make me enable %r (I rejected it)\r\n" % (option,))
+ return False
+
+
+ def disableLocal(self, option):
+ self.transport.write("You asked me to disable %r\r\n" % (option,))
+
+
+ def dataReceived(self, data):
+ self.transport.write("I received %r from you\r\n" % (data,))
+
+
+factory = ServerFactory()
+factory.protocol = lambda: TelnetTransport(TelnetEcho)
+service = TCPServer(8023, factory)
+
+application = Application("Telnet Echo Server")
+service.setServiceParent(application)
diff --git a/doc/conch/examples/window.tac b/doc/conch/examples/window.tac
new file mode 100644
index 0000000..5946a48
--- /dev/null
+++ b/doc/conch/examples/window.tac
@@ -0,0 +1,202 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Widgets demo.
+
+You can run this .tac file directly with:
+ twistd -ny window.tac
+
+Demonstrates various widgets or buttons, such as scrollable regions,
+drawable canvas, etc.
+
+This demo sets up two listening ports: one on 6022 which accepts ssh
+connections; one on 6023 which accepts telnet connections. No login for the
+telnet server is required; for the ssh server, "username" is the username and
+"password" is the password.
+"""
+
+from __future__ import division
+
+import string, random
+
+from twisted.python import log
+from twisted.internet import protocol, task
+from twisted.application import internet, service
+from twisted.cred import checkers, portal
+
+from twisted.conch.insults import insults, window
+from twisted.conch.telnet import TelnetTransport, TelnetBootstrapProtocol
+from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
+
+from twisted.internet import reactor
+
+class DrawableCanvas(window.Canvas):
+ x = 0
+ y = 0
+
+ def func_LEFT_ARROW(self, modifier):
+ self.x -= 1
+ self.repaint()
+
+ def func_RIGHT_ARROW(self, modifier):
+ self.x += 1
+ self.repaint()
+
+ def func_UP_ARROW(self, modifier):
+ self.y -= 1
+ self.repaint()
+
+ def func_DOWN_ARROW(self, modifier):
+ self.y += 1
+ self.repaint()
+
+ def characterReceived(self, keyID, modifier):
+ self[self.x, self.y] = keyID
+ self.x += 1
+ self.repaint()
+
+ def keystrokeReceived(self, keyID, modifier):
+ if keyID == '\r' or keyID == '\v':
+ return
+ window.Canvas.keystrokeReceived(self, keyID, modifier)
+ if self.x >= self.width:
+ self.x = 0
+ elif self.x < 0:
+ self.x = self.width - 1
+
+ if self.y >= self.height:
+ self.y = 0
+ elif self.y < 0:
+ self.y = self.height - 1
+ self.repaint()
+
+ def render(self, width, height, terminal):
+ window.Canvas.render(self, width, height, terminal)
+ if self.focused:
+ terminal.cursorPosition(self.x, self.y)
+ window.cursor(terminal, self[self.x, self.y])
+
+
+class ButtonDemo(insults.TerminalProtocol):
+ width = 80
+ height = 24
+
+ def _draw(self):
+ self.window.draw(self.width, self.height, self.terminal)
+
+ def _redraw(self):
+ self.window.filthy()
+ self._draw()
+
+ def _schedule(self, f):
+ reactor.callLater(0, f)
+
+ def connectionMade(self):
+ self.terminal.eraseDisplay()
+ self.terminal.resetPrivateModes([insults.privateModes.CURSOR_MODE])
+
+ self.window = window.TopWindow(self._draw, self._schedule)
+ self.output = window.TextOutput((15, 1))
+ self.input = window.TextInput(15, self._setText)
+ self.select1 = window.Selection(map(str, range(100)), self._setText, 10)
+ self.select2 = window.Selection(map(str, range(200, 300)), self._setText, 10)
+ self.button = window.Button("Clear", self._clear)
+ self.canvas = DrawableCanvas()
+
+ hbox = window.HBox()
+ hbox.addChild(self.input)
+ hbox.addChild(self.output)
+ hbox.addChild(window.Border(self.button))
+ hbox.addChild(window.Border(self.select1))
+ hbox.addChild(window.Border(self.select2))
+
+ t1 = window.TextOutputArea(longLines=window.TextOutputArea.WRAP)
+ t2 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t3 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ t4 = window.TextOutputArea(longLines=window.TextOutputArea.TRUNCATE)
+ for _t in t1, t2, t3, t4:
+ _t.setText((('This is a very long string. ' * 3) + '\n') * 3)
+
+ vp = window.Viewport(t3)
+ d = [1]
+ def spin():
+ vp.xOffset += d[0]
+ if vp.xOffset == 0 or vp.xOffset == 25:
+ d[0] *= -1
+ self.call = task.LoopingCall(spin)
+ self.call.start(0.25, now=False)
+ hbox.addChild(window.Border(vp))
+
+ vp2 = window.ScrolledArea(t4)
+ hbox.addChild(vp2)
+
+ texts = window.VBox()
+ texts.addChild(window.Border(t1))
+ texts.addChild(window.Border(t2))
+
+ areas = window.HBox()
+ areas.addChild(window.Border(self.canvas))
+ areas.addChild(texts)
+
+ vbox = window.VBox()
+ vbox.addChild(hbox)
+ vbox.addChild(areas)
+ self.window.addChild(vbox)
+ self.terminalSize(self.width, self.height)
+
+ def connectionLost(self, reason):
+ self.call.stop()
+ insults.TerminalProtocol.connectionLost(self, reason)
+
+ def terminalSize(self, width, height):
+ self.width = width
+ self.height = height
+ self.terminal.eraseDisplay()
+ self._redraw()
+
+
+ def keystrokeReceived(self, keyID, modifier):
+ self.window.keystrokeReceived(keyID, modifier)
+
+ def _clear(self):
+ self.canvas.clear()
+
+ def _setText(self, text):
+ self.input.setText('')
+ self.output.setText(text)
+
+
+def makeService(args):
+ checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(username="password")
+
+ f = protocol.ServerFactory()
+ f.protocol = lambda: TelnetTransport(TelnetBootstrapProtocol,
+ insults.ServerProtocol,
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+ tsvc = internet.TCPServer(args['telnet'], f)
+
+ def chainProtocolFactory():
+ return insults.ServerProtocol(
+ args['protocolFactory'],
+ *args.get('protocolArgs', ()),
+ **args.get('protocolKwArgs', {}))
+
+ rlm = TerminalRealm()
+ rlm.chainedProtocolFactory = chainProtocolFactory
+ ptl = portal.Portal(rlm, [checker])
+ f = ConchFactory(ptl)
+ csvc = internet.TCPServer(args['ssh'], f)
+
+ m = service.MultiService()
+ tsvc.setServiceParent(m)
+ csvc.setServiceParent(m)
+ return m
+
+application = service.Application("Window Demo")
+
+makeService({'protocolFactory': ButtonDemo,
+ 'telnet': 6023,
+ 'ssh': 6022}).setServiceParent(application)
diff --git a/doc/conch/howto/conch_client.html b/doc/conch/howto/conch_client.html
new file mode 100644
index 0000000..cf88a1a
--- /dev/null
+++ b/doc/conch/howto/conch_client.html
@@ -0,0 +1,321 @@
+
+
+Twisted Documentation: Writing a client with Twisted Conch
+
+
+
+
+ Writing a client with Twisted Conch
+
+
+
+
+
Introduction
+
+
In the original days of computing, rsh/rlogin were used to connect to
+remote computers and execute commands. These commands had the problem
+that the passwords and commands were sent in the clear. To solve this
+problem, the SSH protocol was created. Twisted Conch implements the
+second version of this protocol.
+
+
Writing a client
+
+
Writing a client with Conch involves sub-classing 4 classes: twisted.conch.ssh.transport.SSHClientTransport
, twisted.conch.ssh.userauth.SSHUserAuthClient
, twisted.conch.ssh.connection.SSHConnection
, and twisted.conch.ssh.channel.SSHChannel
. We'll start out
+with SSHClientTransport
because it's the base
+of the client.
+
+
The Transport
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+
from twisted .conch import error
+from twisted .conch .ssh import transport
+from twisted .internet import defer
+
+class ClientTransport (transport .SSHClientTransport ):
+
+ def verifyHostKey (self , pubKey , fingerprint ):
+ if fingerprint != 'b1:94:6a:c9:24:92:d2:34:7c:62:35:b4:d2:61:11:84' :
+ return defer .fail (error .ConchError ('bad key' ))
+ else :
+ return defer .succeed (1 )
+
+ def connectionSecure (self ):
+ self .requestService (ClientUserAuth ('user' , ClientConnection ()))
+
+
+
See how easy it is? SSHClientTransport
+handles the negotiation of encryption and the verification of keys
+for you. The one security element that you as a client writer need to
+implement is verifyHostKey()
. This method
+is called with two strings: the public key sent by the server and its
+fingerprint. You should verify the host key the server sends, either
+by checking against a hard-coded value as in the example, or by asking
+the user. verifyHostKey
returns a twisted.internet.defer.Deferred
which gets a callback
+if the host key is valid, or an errback if it is not. Note that in the
+above, replace 'user' with the username you're attempting to ssh with,
+for instance a call to os.getlogin()
for the
+current user.
+
+
The second method you need to implement is connectionSecure()
. It is called when the
+encryption is set up and other services can be run. The example requests
+that the ClientUserAuth
service be started.
+This service will be discussed next.
+
+
The Authorization Client
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from twisted .conch .ssh import keys , userauth
+
+
+
+publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEArzJx8OYOnJmzf4tfBEvLi8DVPrJ3\
+/c9k2I/Az64fxjHf9imyRJbixtQhlH9lfNjUIx+4LmrJH5QNRsFporcHDKOTwTTYLh5KmRpslkYHR\
+ivcJSkbh/C+BR3utDS555mV'
+
+privateKey = """-----BEGIN RSA PRIVATE KEY-----
+MIIByAIBAAJhAK8ycfDmDpyZs3+LXwRLy4vA1T6yd/3PZNiPwM+uH8Yx3/YpskSW
+4sbUIZR/ZXzY1CMfuC5qyR+UDUbBaaK3Bwyjk8E02C4eSpkabJZGB0Yr3CUpG4fw
+vgUd7rQ0ueeZlQIBIwJgbh+1VZfr7WftK5lu7MHtqE1S1vPWZQYE3+VUn8yJADyb
+Z4fsZaCrzW9lkIqXkE3GIY+ojdhZhkO1gbG0118sIgphwSWKRxK0mvh6ERxKqIt1
+xJEJO74EykXZV4oNJ8sjAjEA3J9r2ZghVhGN6V8DnQrTk24Td0E8hU8AcP0FVP+8
+PQm/g/aXf2QQkQT+omdHVEJrAjEAy0pL0EBH6EVS98evDCBtQw22OZT52qXlAwZ2
+gyTriKFVoqjeEjt3SZKKqXHSApP/AjBLpF99zcJJZRq2abgYlf9lv1chkrWqDHUu
+DZttmYJeEfiFBBavVYIF1dOlZT0G8jMCMBc7sOSZodFnAiryP+Qg9otSBjJ3bQML
+pSTqy7c3a2AScC/YyOwkDaICHnnD3XyjMwIxALRzl0tQEKMXs6hH8ToUdlLROCrP
+EhQ0wahUTCk1gKA4uPD6TMTChavbh4K63OvbKg==
+-----END RSA PRIVATE KEY-----"""
+
+class ClientUserAuth (userauth .SSHUserAuthClient ):
+
+ def getPassword (self , prompt = None ):
+ return
+
+
+ def getPublicKey (self ):
+ return keys .Key .fromString (data = publicKey ).blob ()
+
+ def getPrivateKey (self ):
+ return defer .succeed (keys .Key .fromString (data = privateKey ).keyObject )
+
+
+
Again, fairly simple. The SSHUserAuthClient
takes care of most
+of the work, but the actual authentication data needs to be
+supplied. getPassword()
asks for a
+password, getPublicKey()
and getPrivateKey()
get public and private keys,
+respectively. getPassword()
returns
+a Deferred
that is called back with
+the password to use. getPublicKey()
+returns the SSH key data for the public key to use. keys.Key.fromString()
will take
+a key in OpenSSH or LSH format as a string, and convert it to the
+required format. Alternatively, keys.Key.fromFile()
can be used instead, which
+will take the filename of a key in OpenSSH and LSH format, and
+convert it to the required format.
+getPrivateKey()
+returns a Deferred
which is
+called back with the key object (as used in PyCrypto) for
+the private key. getPassword()
+and getPrivateKey()
return Deferreds
because they may need to ask the user
+for input.
+
+
Once the authentication is complete, SSHUserAuthClient
takes care of starting the code
+SSHConnection
object given to it. Next, we'll
+look at how to use the SSHConnection
+
+
The Connection
+
+
1
+2
+3
+4
+5
+6
+
from twisted .conch .ssh import connection
+
+class ClientConnection (connection .SSHConnection ):
+
+ def serviceStarted (self ):
+ self .openChannel (CatChannel (conn = self ))
+
+
+
SSHConnection
is the easiest,
+as it's only responsible for starting the channels. It has
+other methods, those will be examined when we look at SSHChannel
.
+
+
The Channel
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
from twisted .conch .ssh import channel , common
+
+class CatChannel (channel .SSHChannel ):
+
+ name = 'session'
+
+ def channelOpen (self , data ):
+ d = self .conn .sendRequest (self , 'exec' , common .NS ('cat' ),
+ wantReply = 1 )
+ d .addCallback (self ._cbSendRequest )
+ self .catData = ''
+
+ def _cbSendRequest (self , ignored ):
+ self .write ('This data will be echoed back to us by "cat."\r\n' )
+ self .conn .sendEOF (self )
+ self .loseConnection ()
+
+ def dataReceived (self , data ):
+ self .catData += data
+
+ def closed (self ):
+ print 'We got this from "cat":' , self .catData
+
+
+
Now that we've spent all this time getting the server and
+client connected, here is where that work pays off. SSHChannel
is the interface between you and the
+other side. This particular channel opens a session and plays with the
+'cat' program, but your channel can implement anything, so long as the
+server supports it.
+
+
The channelOpen()
method is
+where everything gets started. It gets passed a chunk of data;
+however, this chunk is usually nothing and can be ignored.
+Our channelOpen()
initializes our
+channel, and sends a request to the other side, using the
+sendRequest()
method of the SSHConnection
object. Requests are used to send
+events to the other side. We pass the method self so that it knows to
+send the request for this channel. The 2nd argument of 'exec' tells the
+server that we want to execute a command. The third argument is the data
+that accompanies the request.
+common.NS
encodes
+the data as a length-prefixed string, which is how the server expects
+the data. We also say that we want a reply saying that the process has a
+been started. sendRequest()
then returns a
+Deferred
which we add a callback for.
+
+
Once the callback fires, we send the data. SSHChannel
supports the
+twisted.internet.interfaces.ITransport
+interface, so
+it can be given to Protocols to run them over the secure
+connection. In our case, we just write the data directly. sendEOF()
does not follow the interface,
+but Conch uses it to tell the other side that we will write no
+more data. loseConnection()
shuts
+down our side of the connection, but we will still receive data
+through dataReceived()
. The closed()
method is called when both sides of the
+connection are closed, and we use it to display the data we received
+(which should be the same as the data we sent.)
+
+
Finally, let's actually invoke the code we've set up.
+
+
The main() function
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
from twisted .internet import protocol , reactor
+
+def main ():
+ factory = protocol .ClientFactory ()
+ factory .protocol = ClientTransport
+ reactor .connectTCP ('localhost' , 22 , factory )
+ reactor .run ()
+
+if __name__ == "__main__" :
+ main ()
+
+
+
We call connectTCP()
to connect to
+localhost, port 22 (the standard port for ssh), and pass it an instance
+of twisted.internet.protocol.ClientFactory
.
+This instance has the attribute protocol
+set to our earlier ClientTransport
+class. Note that the protocol attribute is set to the class ClientTransport
, not an instance of
+ClientTransport
! When the connectTCP
call completes, the protocol will be
+called to create a ClientTransport()
object
+- this then invokes all our previous work.
+
+
It's worth noting that in the example main()
+routine, the reactor.run()
call never returns.
+If you want to make the program exit, call
+reactor.stop()
in the earlier
+closed()
method.
+
+
If you wish to observe the interactions in more detail, adding a call
+to log.startLogging(sys.stdout, setStdout=0)
+before the reactor.run()
call will send all
+logging to stdout.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/howto/index.html b/doc/conch/howto/index.html
new file mode 100644
index 0000000..0782a48
--- /dev/null
+++ b/doc/conch/howto/index.html
@@ -0,0 +1,28 @@
+
+
+Twisted Documentation: Twisted Documentation
+
+
+
+
+ Twisted Documentation
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/index.html b/doc/conch/index.html
new file mode 100644
index 0000000..9416896
--- /dev/null
+++ b/doc/conch/index.html
@@ -0,0 +1,25 @@
+
+
+Twisted Documentation: Twisted Conch Documentation
+
+
+
+
+ Twisted Conch Documentation
+
+
+
+
+
+Developer guides : documentation on using
+Twisted Conch to develop your own applications
+Examples : short code examples using
+Twisted Conch
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/man/cftp-man.html b/doc/conch/man/cftp-man.html
new file mode 100644
index 0000000..5c20c00
--- /dev/null
+++ b/doc/conch/man/cftp-man.html
@@ -0,0 +1,87 @@
+
+
+Twisted Documentation: CFTP.1
+
+
+
+
+ CFTP.1
+
+
+
+
+
+
NAME
+
+
cftp
+
+
SYNOPSIS
+
+
cftp [-B buffer_size ][-b command_file ][-R num_requests ][-s subsystem ]
+
+
DESCRIPTION
+
+
cftp is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+
+
+
The options are as follows:
+
-B Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+ -b File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+ -R Number of requests to make before waiting for a reply.
+ -s Subsystem/server program to connect to.
+
+
+
+
+
The following commands are recognised by
+cftp :
+
cd path Change the remote directory to 'path'.
+ chgrp gid path Change the gid of 'path' to 'gid'.
+ chmod mode path Change mode of 'path' to 'mode'.
+ chown uid path Change uid of 'path' to 'uid'.
+ exit Disconnect from the server.
+ get remote-path [local-path ] Get remote file and optionally store it at specified local path.
+ help Get a list of available commands.
+ lcd path Change local directory to 'path'.
+ lls [ls-options ] [path ] Display local directory listing.
+ lmkdir path Create local directory.
+ ln linkpath targetpath Symlink remote file.
+ lpwd Print the local working directory.
+ ls [-l ] [path ] Display remote directory listing.
+ mkdir path Create remote directory.
+ progress Toggle progress bar.
+ put local-path [remote-path ] Transfer local file to remote location
+ pwd Print the remote working directory.
+ quit Disconnect from the server.
+ rename oldpath newpath Rename remote file.
+ rmdir path Remove remote directory.
+ rm path Remove remote file.
+ version Print the SFTP version.
+ ? Synonym for 'help'.
+
+
+
+
+
AUTHOR
+
+
cftp by Paul Swartz <z3p@twistedmatrix.com>. Man page by Mary Gardiner <mary@twistedmatrix.com>.
+
+
+
REPORTING BUGS
+
+
Report bugs to http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2005-2008 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/man/cftp.1 b/doc/conch/man/cftp.1
new file mode 100644
index 0000000..7eae889
--- /dev/null
+++ b/doc/conch/man/cftp.1
@@ -0,0 +1,89 @@
+.Dd October 8, 2005
+.Dt CFTP 1
+.Os
+.Sh NAME
+.Nm cftp
+.Nd Conch command-line SFTP client
+.Sh SYNOPSIS
+.Nm cftp
+.Op Fl B Ar buffer_size
+.Op Fl b Ar command_file
+.Op Fl R Ar num_requests
+.Op Fl s Ar subsystem
+.Os
+.Sh DESCRIPTION
+.Nm
+is a client for logging into a remote machine and executing commands to send and receive file information. It can wrap a number of file transfer subsystems
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Specifies the default size of the buffer to use for sending and receiving. (Default value: 32768 bytes.)
+.It Fl b
+File to read commands from, '-' for stdin. (Default value: interactive/stdin.)
+.It Fl R
+Number of requests to make before waiting for a reply.
+.It Fl s
+Subsystem/server program to connect to.
+.El
+.Pp
+The following commands are recognised by
+.Nm
+:
+.Bl -tag -width Ds
+.It Ic cd Ar path
+Change the remote directory to 'path'.
+.It Ic chgrp Ar gid Ar path
+Change the gid of 'path' to 'gid'.
+.It Ic chmod Ar mode Ar path
+Change mode of 'path' to 'mode'.
+.It Ic chown Ar uid Ar path
+Change uid of 'path' to 'uid'.
+.It Ic exit
+Disconnect from the server.
+.It Ic get Ar remote-path Op Ar local-path
+Get remote file and optionally store it at specified local path.
+.It Ic help
+Get a list of available commands.
+.It Ic lcd Ar path
+Change local directory to 'path'.
+.It Ic lls Op Ar ls-options Op Ar path
+Display local directory listing.
+.It Ic lmkdir Ar path
+Create local directory.
+.It Ic ln Ar linkpath Ar targetpath
+Symlink remote file.
+.It Ic lpwd
+Print the local working directory.
+.It Ic ls Op Ar -l Op Ar path
+Display remote directory listing.
+.It Ic mkdir Ar path
+Create remote directory.
+.It Ic progress
+Toggle progress bar.
+.It Ic put Ar local-path Op Ar remote-path
+Transfer local file to remote location
+.It Ic pwd
+Print the remote working directory.
+.It Ic quit
+Disconnect from the server.
+.It Ic rename Ar oldpath Ar newpath
+Rename remote file.
+.It Ic rmdir Ar path
+Remove remote directory.
+.It Ic rm Ar path
+Remove remote file.
+.It Ic version
+Print the SFTP version.
+.It Ic ?
+Synonym for 'help'.
+.El
+.Sh AUTHOR
+cftp by Paul Swartz . Man page by Mary Gardiner .
+.Sh "REPORTING BUGS"
+Report bugs to \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2005-2008 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/conch/man/ckeygen-man.html b/doc/conch/man/ckeygen-man.html
new file mode 100644
index 0000000..e16fe32
--- /dev/null
+++ b/doc/conch/man/ckeygen-man.html
@@ -0,0 +1,107 @@
+
+
+Twisted Documentation: CKEYGEN.1
+
+
+
+
+ CKEYGEN.1
+
+
+
+
+
+
NAME
+
+
ckeygen - manipulate public/private keys
+
+
+
SYNOPSIS
+
+
ckeygen [-b bits ] [-f filename ] [-t type ][-C comment ] [-N new passphrase ] [-P old passphrase ][-l] [-p] [-q] [-y]
+
+
DESCRIPTION
+
+
The --help prints out a usage message to standard output.
+
-b , --bits <bits>
+Number of bits in the key to create (default: 1024)
+
+
+-f , --filename <file name>
+Filename of the key file.
+
+
+-t , --type <type>
+Type of key (rsa or dsa).
+
+
+-C , --comment <comment>
+Provide a new comment.
+
+
+-N , --newpass <pass phrase>
+Provide new passphrase.
+
+
+-P , --pass <pass phrase>
+Provide old passphrase.
+
+
+-l , --fingerprint
+Show fingerprint of key file.
+
+
+-p , --changepass
+Change passphrase of private key file.
+
+
+-q , --quiet
+Be quiet.
+
+
+-y , --showpub
+Read private key file and print public key.
+
+
+--version
+Display version number only.
+
+
+
+
+
+
+
DESCRIPTION
+
+
Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+
+
+
AUTHOR
+
+
Written by Moshe Zadka, based on ckeygen's help messages
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2002-2011 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
SEE ALSO
+
+
ssh(1), conch(1)
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/man/ckeygen.1 b/doc/conch/man/ckeygen.1
new file mode 100644
index 0000000..04d720f
--- /dev/null
+++ b/doc/conch/man/ckeygen.1
@@ -0,0 +1,57 @@
+.TH CKEYGEN "1" "October 2002" "" ""
+.SH NAME
+ckeygen \- manipulate public/private keys
+.SH SYNOPSIS
+.B ckeygen [-b \fIbits\fR] [-f \fIfilename\fR] [-t \fItype\fR]
+.B [-C \fIcomment\fR] [-N \fInew passphrase\fR] [-P \fIold passphrase\fR]
+.B [-l] [-p] [-q] [-y]
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-b\fR, \fB--bits\fR
+Number of bits in the key to create (default: 1024)
+.TP
+\fB-f\fR, \fB--filename\fR
+Filename of the key file.
+.TP
+\fB-t\fR, \fB--type\fR
+Type of key (rsa or dsa).
+.TP
+\fB-C\fR, \fB--comment\fR
+Provide a new comment.
+.TP
+\fB-N\fR, \fB--newpass\fR
+Provide new passphrase.
+.TP
+\fB-P\fR, \fB--pass\fR
+Provide old passphrase.
+.TP
+\fB-l\fR, \fB--fingerprint\fR
+Show fingerprint of key file.
+.TP
+\fB-p\fR, \fB--changepass\fR
+Change passphrase of private key file.
+.TP
+\fB-q\fR, \fB--quiet\fR
+Be quiet.
+.TP
+\fB-y\fR, \fB--showpub\fR
+Read private key file and print public key.
+.TP
+\fB--version\fR
+Display version number only.
+.SH DESCRIPTION
+Manipulate public/private keys in various ways.
+If no filename is given, a file name will be requested interactively.
+.SH AUTHOR
+Written by Moshe Zadka, based on ckeygen's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2011 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1), conch(1)
diff --git a/doc/conch/man/conch-man.html b/doc/conch/man/conch-man.html
new file mode 100644
index 0000000..abf5a98
--- /dev/null
+++ b/doc/conch/man/conch-man.html
@@ -0,0 +1,148 @@
+
+
+Twisted Documentation: CONCH.1
+
+
+
+
+ CONCH.1
+
+
+
+
+
+
NAME
+
+
conch
+
+
SYNOPSIS
+
+
conch [-AaCfINnrsTtVvx ][-c cipher_spec ][-e escape_char ][-i identity_file ][-K connection_spec ][-L port : host : hostport ][-l user ][-m mac_spec ][-o openssh_option ][-p port ][-R port : host : hostport ][ user @] hostname [ command ]
+
+
DESCRIPTION
+
+
conch is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+
+
+
conch connects and logs into
+ hostname (as
+ user or the current username). The user must prove her/his identity through a public-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+
+
+
If
+ command is specified,
+ command is executed instead of a shell. If the
+-s option is given,
+ command is treated as an SSHv2 subsystem name.
+Conch supports the public-key, keyboard-interactive, and password authentications.
+
+
+
The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+or
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+
+
+
If public-key authentication fails,
+conch can authenticate by sending an encrypted password over the connection.
+conch has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+-I flag.
+
+
+
The
+-K option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+(for connecting over a multiplexed connection) and
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+in the preference list.
+
+
+
As an example of how connection sharing works, to speed up CVS over SSH:
+
+
+
conch --noshell --fork -l cvs_user cvs_host
+set CVS_RSH=conch
+
+
+
Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+conch will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+
+
+
The options are as follows:
+
-A Enables authentication agent forwarding.
+ -a Disables authentication agent forwarding (default).
+ -C Enable compression.
+ -c cipher_spec Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+conch supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+-e ch | ^ch | noneSets the escape character for sessions with a PTY (default:
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+disables any escapes.
+-f Fork to background after authentication.
+ -I Do not allow connection sharing over this connection.
+ -i identity_spec The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+and
+It is possible to use this option more than once to use more than one private key.
+-K connection_spec Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+for more information.
+-L port : host : hostportSpecifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+ port on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+ host port
+ hostport from the remote machine.
+Only root can forward privieged ports.
+-l user Log in using this username.
+-m mac_spec Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+conch supports is (in order of preference): hmac-sha1, hmac-md5.
+-N Do not execute a shell or command.
+ -n Redirect input from /dev/null.
+ -o openssh_option Ignored OpenSSH options.
+-p port The port to connect to on the server.
+-R port : host : hostportSpecifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+ port on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+ host port
+ hostport from the client host.
+Only root can forward privieged ports.
+-s Reconnect to the server if the connection is lost.
+ -s Invoke
+ command (mandatory) as a SSHv2 subsystem.
+ -T Do not allocate a TTY.
+ -t Allocate a TTY even if command is given.
+ -V Display version number only.
+ -v Log to stderr.
+ -x Disable X11 connection forwarding (default).
+
+
+
+
+
AUTHOR
+
+
Written by Paul Swartz <z3p@twistedmatrix.com>.
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
SEE ALSO
+
+
ssh(1)
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/man/conch.1 b/doc/conch/man/conch.1
new file mode 100644
index 0000000..7ba9bff
--- /dev/null
+++ b/doc/conch/man/conch.1
@@ -0,0 +1,206 @@
+.Dd May 22, 2004
+.Dt CONCH 1
+.Os
+.Sh NAME
+.Nm conch
+.Nd Conch SSH client
+.Sh SYNOPSIS
+.Nm conch
+.Op Fl AaCfINnrsTtVvx
+.Op Fl c Ar cipher_spec
+.Op Fl e Ar escape_char
+.Op Fl i Ar identity_file
+.Op Fl K Ar connection_spec
+.Bk -words
+.Oo Fl L Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Op Fl l Ar user
+.Op Fl m Ar mac_spec
+.Op Fl o Ar openssh_option
+.Op Fl p Ar port
+.Bk -words
+.Oo Fl R Xo
+.Sm off
+.Ar port :
+.Ar host :
+.Ar hostport
+.Sm on
+.Xc
+.Oc
+.Ek
+.Oo Ar user Ns @ Ns Oc Ar hostname
+.Op Ar command
+.Sh DESCRIPTION
+.Nm
+is a SSHv2 client for logging into a remote machine and executing commands. It provides encrypted and secure communications across a possibly insecure network. Arbitrary TCP/IP ports can also be forwarded over the secure connection.
+.Pp
+.Nm
+connects and logs into
+.Ar hostname
+(as
+.Ar user
+or the current username). The user must prove her/his identity through a public\-key or a password. Alternatively, if a connection is already open to a server, a new shell can be opened over the connection without having to reauthenticate.
+.Pp
+If
+.Ar command
+is specified,
+.Ar command
+is executed instead of a shell. If the
+.Fl s
+option is given,
+.Ar command
+is treated as an SSHv2 subsystem name.
+.Ss Authentication
+Conch supports the public-key, keyboard-interactive, and password authentications.
+.Pp
+The public-key method allows the RSA or DSA algorithm to be used. The client uses his/her private key,
+.Pa $HOME/.ssh/id_rsa
+or
+.Pa $HOME/.ssh/id_dsa
+to sign the session identifier, known only by the client and server. The server checks that the matching public key is valid for the user, and that the signature is correct.
+.Pp
+If public-key authentication fails,
+.Nm
+can authenticate by sending an encrypted password over the connection.
+.Ss Connection sharing
+.Nm
+has the ability to multiplex multiple shells, commands and TCP/IP ports over the same secure connection. To disable multiplexing for a connection, use the
+.Fl I
+flag.
+.Pp
+The
+.Fl K
+option determines how the client connects to the remote host. It is a comma-separated list of the methods to use, in order of preference. The two connection methods are
+.Ql unix
+(for connecting over a multiplexed connection) and
+.Ql direct
+(to connect directly).
+To disable connecting over a multiplexed connection, do not include
+.Ql unix
+in the preference list.
+.Pp
+As an example of how connection sharing works, to speed up CVS over SSH:
+.Pp
+.Nm
+--noshell --fork -l cvs_user cvs_host
+.br
+set CVS_RSH=\fBconch\fR
+.Pp
+Now, when CVS connects to cvs_host as cvs_user, instead of making a new connection to the server,
+.Nm
+will add a new channel to the existing connection. This saves the cost of repeatedly negotiating the cryptography and authentication.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Enables authentication agent forwarding.
+.It Fl a
+Disables authentication agent forwarding (default).
+.It Fl C
+Enable compression.
+.It Fl c Ar cipher_spec
+Selects encryption algorithms to be used for this connection, as a comma-separated list of ciphers in order of preference. The list that
+.Nm
+supports is (in order of default preference): aes256-ctr, aes256-cbc, aes192-ctr, aes192-cbc, aes128-ctr, aes128-cbc, cast128-ctr, cast128-cbc, blowfish-ctr, blowfish, idea-ctr, idea-cbc, 3des-ctr, 3des-cbc.
+.It Fl e Ar ch | ^ch | none
+Sets the escape character for sessions with a PTY (default:
+.Ql ~ ) .
+The escape character is only recognized at the beginning of a line (after a newline).
+The escape character followed by a dot
+.Pq Ql \&.
+closes the connection;
+followed by ^Z suspends the connection;
+and followed by the escape character sends the escape character once.
+Setting the character to
+.Dq none
+disables any escapes.
+.It Fl f
+Fork to background after authentication.
+.It Fl I
+Do not allow connection sharing over this connection.
+.It Fl i Ar identity_spec
+The file from which the identity (private key) for RSA or DSA authentication is read.
+The defaults are
+.Pa $HOME/.ssh/id_rsa
+and
+.Pa $HOME/.ssh/id_dsa .
+It is possible to use this option more than once to use more than one private key.
+.It Fl K Ar connection_spec
+Selects methods for connection to the server, as a comma-separated list of methods in order of preference. See
+.Cm Connection sharing
+for more information.
+.It Fl L Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the client host is to be forwarded to the given host and port on the remote side. This allocates a socket to listen to
+.Ar port
+on the local side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the remote machine.
+Only root can forward privieged ports.
+.It Fl l Ar user
+Log in using this username.
+.It Fl m Ar mac_spec
+Selects MAC (message authentication code) algorithms, as a comma-separated list in order of preference. The list that
+.Nm
+supports is (in order of preference): hmac-sha1, hmac-md5.
+.It Fl N
+Do not execute a shell or command.
+.It Fl n
+Redirect input from /dev/null.
+.It Fl o Ar openssh_option
+Ignored OpenSSH options.
+.It Fl p Ar port
+The port to connect to on the server.
+.It Fl R Xo
+.Sm off
+.Ar port : host : hostport
+.Sm on
+.Xc
+Specifies that the given port on the remote host is to be forwarded to the given host and port on the local side. This allocates a socket to listen to
+.Ar port
+on the remote side, and when connections are made to that socket, they are forwarded over the secure channel and a connection is made to
+.Ar host
+port
+.Ar hostport
+from the client host.
+Only root can forward privieged ports.
+.It Fl s
+Reconnect to the server if the connection is lost.
+.It Fl s
+Invoke
+.Ar command
+(mandatory) as a SSHv2 subsystem.
+.It Fl T
+Do not allocate a TTY.
+.It Fl t
+Allocate a TTY even if command is given.
+.It Fl V
+Display version number only.
+.It Fl v
+Log to stderr.
+.It Fl x
+Disable X11 connection forwarding (default).
+.El
+.Sh AUTHOR
+Written by Paul Swartz .
+.Sh "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.Sh COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.Sh SEE ALSO
+ssh(1)
diff --git a/doc/conch/man/tkconch-man.html b/doc/conch/man/tkconch-man.html
new file mode 100644
index 0000000..41ae30a
--- /dev/null
+++ b/doc/conch/man/tkconch-man.html
@@ -0,0 +1,129 @@
+
+
+Twisted Documentation: CONCH.1
+
+
+
+
+ CONCH.1
+
+
+
+
+
+
NAME
+
+
tkconch - connect to SSH servers graphically
+
+
+
SYNOPSIS
+
+
conch [-l user ] [-i identity [ -i identity ... ]] [-c cipher ] [-m MAC ] [-p port ] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]
+
+
conch --help
+
+
DESCRIPTION
+
+
The --help prints out a usage message to standard output.
+
-l , --user <user>
+Log in using this user name.
+
+
+-e , --escape <escape character>
+Set escape character; 'none' = disable (default: ~)
+
+
+-i , --identity <identity>
+Add an identity file for public key authentication (default: ~/.ssh/identity)
+
+
+-c , --cipher <cipher>
+Cipher algorithm to use.
+
+
+-m , --macs <mac>
+Specify MAC algorithms for protocol version 2.
+
+
+-p , --port <port>
+Port to connect to.
+
+
+-L , --localforward <listen-port:host:port>
+Forward local port to remote address.
+
+
+-R , --remoteforward <listen-port:host:port>
+Forward remote port to local address.
+
+
+-t , --tty
+Allocate a tty even if command is given.
+
+
+-n , --notty
+Do not allocate a tty.
+
+
+-V , --version
+Display version number only.
+
+
+-C , --compress
+Enable compression.
+
+
+-a , --ansilog
+Print the received data to stdout.
+
+
+-N , --noshell
+Do not execute a shell or command.
+
+
+-s , --subsystem
+Invoke command (mandatory) as SSH2 subsystem.
+
+
+--log
+Print the receieved data to stderr.
+
+
+
+
+
+
+
DESCRIPTION
+
+
Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+
+
+
AUTHOR
+
+
Written by Moshe Zadka, based on conch's help messages
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2002-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
SEE ALSO
+
+
ssh(1)
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/conch/man/tkconch.1 b/doc/conch/man/tkconch.1
new file mode 100644
index 0000000..54260bf
--- /dev/null
+++ b/doc/conch/man/tkconch.1
@@ -0,0 +1,72 @@
+.TH CONCH "1" "October 2002" "" ""
+.SH NAME
+tkconch \- connect to SSH servers graphically
+.SH SYNOPSIS
+.B conch [-l \fIuser\fR] [-i \fIidentity\fR [ -i \fIidentity\fR ... ]] [-c \fIcipher\fR] [-m \fIMAC\fR] [-p \fIport\fR] [-n] [-t] [-T] [-V] [-C] [-N] [-s] [arg [...]]
+.PP
+.B conch --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB-l\fR, \fB--user\fR
+Log in using this user name.
+.TP
+\fB-e\fR, \fB--escape\fR
+Set escape character; 'none' = disable (default: ~)
+.TP
+\fB-i\fR, \fB--identity\fR
+Add an identity file for public key authentication (default: ~/.ssh/identity)
+.TP
+\fB-c\fR, \fB--cipher\fR
+Cipher algorithm to use.
+.TP
+\fB-m\fR, \fB--macs\fR
+Specify MAC algorithms for protocol version 2.
+.TP
+\fB-p\fR, \fB--port\fR
+Port to connect to.
+.TP
+\fB-L\fR, \fB--localforward\fR
+Forward local port to remote address.
+.TP
+\fB-R\fR, \fB--remoteforward\fR
+Forward remote port to local address.
+.TP
+\fB-t\fR, \fB--tty\fR
+Allocate a tty even if command is given.
+.TP
+\fB-n\fR, \fB--notty\fR
+Do not allocate a tty.
+.TP
+\fB-V\fR, \fB--version\fR
+Display version number only.
+.TP
+\fB-C\fR, \fB--compress\fR
+Enable compression.
+.TP
+\fB-a\fR, \fB--ansilog\fR
+Print the received data to stdout.
+.TP
+\fB-N\fR, \fB--noshell\fR
+Do not execute a shell or command.
+.TP
+\fB-s\fR, \fB--subsystem\fR
+Invoke command (mandatory) as SSH2 subsystem.
+.TP
+\fB--log\fR
+Print the receieved data to stderr.
+.SH DESCRIPTION
+Open an SSH connection to specified server, and either run the command
+given there or open a remote interactive shell.
+.SH AUTHOR
+Written by Moshe Zadka, based on conch's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2002-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+ssh(1)
diff --git a/doc/core/benchmarks/banana.py b/doc/core/benchmarks/banana.py
new file mode 100644
index 0000000..1c1f031
--- /dev/null
+++ b/doc/core/benchmarks/banana.py
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+from timer import timeit
+from twisted.spread.banana import b1282int
+
+ITERATIONS = 100000
+
+for length in (1, 5, 10, 50, 100):
+ elapsed = timeit(b1282int, ITERATIONS, "\xff" * length)
+ print "b1282int %3d byte string: %10d cps" % (length, ITERATIONS / elapsed)
diff --git a/doc/core/benchmarks/deferreds.py b/doc/core/benchmarks/deferreds.py
new file mode 100644
index 0000000..ddae19e
--- /dev/null
+++ b/doc/core/benchmarks/deferreds.py
@@ -0,0 +1,145 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+See how fast deferreds are.
+
+This is mainly useful to compare cdefer.Deferred to defer.Deferred
+"""
+
+
+from twisted.internet import defer
+from timer import timeit
+
+benchmarkFuncs = []
+
+def benchmarkFunc(iter, args=()):
+ """
+ A decorator for benchmark functions that measure a single iteration
+ count. Registers the function with the given iteration count to the global
+ benchmarkFuncs list
+ """
+ def decorator(func):
+ benchmarkFuncs.append((func, args, iter))
+ return func
+ return decorator
+
+def benchmarkNFunc(iter, ns):
+ """
+ A decorator for benchmark functions that measure multiple iteration
+ counts. Registers the function with the given iteration count to the global
+ benchmarkFuncs list.
+ """
+ def decorator(func):
+ for n in ns:
+ benchmarkFuncs.append((func, (n,), iter))
+ return func
+ return decorator
+
+def instantiate():
+ """
+ Only create a deferred
+ """
+ d = defer.Deferred()
+instantiate = benchmarkFunc(100000)(instantiate)
+
+def instantiateShootCallback():
+ """
+ Create a deferred and give it a normal result
+ """
+ d = defer.Deferred()
+ d.callback(1)
+instantiateShootCallback = benchmarkFunc(100000)(instantiateShootCallback)
+
+def instantiateShootErrback():
+ """
+ Create a deferred and give it an exception result. To avoid Unhandled
+ Errors, also register an errback that eats the error
+ """
+ d = defer.Deferred()
+ try:
+ 1/0
+ except:
+ d.errback()
+ d.addErrback(lambda x: None)
+instantiateShootErrback = benchmarkFunc(200)(instantiateShootErrback)
+
+ns = [10, 1000, 10000]
+
+def instantiateAddCallbacksNoResult(n):
+ """
+ Creates a deferred and adds a trivial callback/errback/both to it the given
+ number of times.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f, f)
+instantiateAddCallbacksNoResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksNoResult)
+
+def instantiateAddCallbacksBeforeResult(n):
+ """
+ Create a deferred and adds a trivial callback/errback/both to it the given
+ number of times, and then shoots a result through all of the callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.callback(1)
+instantiateAddCallbacksBeforeResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksBeforeResult)
+
+def instantiateAddCallbacksAfterResult(n):
+ """
+ Create a deferred, shoots it and then adds a trivial callback/errback/both
+ to it the given number of times. The result is processed through the
+ callbacks as they are added.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+instantiateAddCallbacksAfterResult = benchmarkNFunc(20, ns)(instantiateAddCallbacksAfterResult)
+
+def pauseUnpause(n):
+ """
+ Adds the given number of callbacks/errbacks/both to a deferred while it is
+ paused, and unpauses it, trigerring the processing of the value through the
+ callbacks.
+ """
+ d = defer.Deferred()
+ def f(result):
+ return result
+ d.callback(1)
+ d.pause()
+ for i in xrange(n):
+ d.addCallback(f)
+ d.addErrback(f)
+ d.addBoth(f)
+ d.addCallbacks(f)
+ d.unpause()
+pauseUnpause = benchmarkNFunc(20, ns)(pauseUnpause)
+
+def benchmark():
+ """
+ Run all of the benchmarks registered in the benchmarkFuncs list
+ """
+ print defer.Deferred.__module__
+ for func, args, iter in benchmarkFuncs:
+ print func.__name__, args, timeit(func, iter, *args)
+
+if __name__ == '__main__':
+ benchmark()
diff --git a/doc/core/benchmarks/failure.py b/doc/core/benchmarks/failure.py
new file mode 100644
index 0000000..d98cb49
--- /dev/null
+++ b/doc/core/benchmarks/failure.py
@@ -0,0 +1,66 @@
+
+"""See how slow failure creation is"""
+
+import random
+from twisted.python import failure
+
+random.seed(10050)
+O = [0, 20, 40, 60, 80, 10, 30, 50, 70, 90]
+DEPTH = 30
+
+def pickVal():
+ return random.choice([None, 1, 'Hello', [], {1: 1}, (1, 2, 3)])
+
+def makeLocals(n):
+ return ';'.join(['x%d = %s' % (i, pickVal()) for i in range(n)])
+
+for nLocals in O:
+ for i in range(DEPTH):
+ s = """
+def deepFailure%d_%d():
+ %s
+ deepFailure%d_%d()
+""" % (nLocals, i, makeLocals(nLocals), nLocals, i + 1)
+ exec s
+
+ exec """
+def deepFailure%d_%d():
+ 1 / 0
+""" % (nLocals, DEPTH)
+
+R = range(5000)
+def fail(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ failure.Failure()
+
+def fail_str(n):
+ for i in R:
+ try:
+ eval('deepFailure%d_0' % n)()
+ except:
+ str(failure.Failure())
+
+class PythonException(Exception): pass
+
+def fail_easy(n):
+ for i in R:
+ try:
+ failure.Failure(PythonException())
+ except:
+ pass
+
+from timer import timeit
+# for i in O:
+# timeit(fail, 1, i)
+
+# for i in O:
+# print 'easy failing', i, timeit(fail_easy, 1, i)
+
+for i in O:
+ print 'failing', i, timeit(fail, 1, i)
+
+# for i in O:
+# print 'string failing', i, timeit(fail_str, 1, i)
diff --git a/doc/core/benchmarks/linereceiver.py b/doc/core/benchmarks/linereceiver.py
new file mode 100644
index 0000000..7f55291
--- /dev/null
+++ b/doc/core/benchmarks/linereceiver.py
@@ -0,0 +1,47 @@
+import math, time
+
+from twisted.protocols import basic
+
+class CollectingLineReceiver(basic.LineReceiver):
+ def __init__(self):
+ self.lines = []
+ self.lineReceived = self.lines.append
+
+def deliver(proto, chunks):
+ map(proto.dataReceived, chunks)
+
+def benchmark(chunkSize, lineLength, numLines):
+ bytes = ('x' * lineLength + '\r\n') * numLines
+ chunkCount = len(bytes) / chunkSize + 1
+ chunks = []
+ for n in xrange(chunkCount):
+ chunks.append(bytes[n*chunkSize:(n+1)*chunkSize])
+ assert ''.join(chunks) == bytes, (chunks, bytes)
+ p = CollectingLineReceiver()
+
+ before = time.clock()
+ deliver(p, chunks)
+ after = time.clock()
+
+ assert bytes.splitlines() == p.lines, (bytes.splitlines(), p.lines)
+
+ print 'chunkSize:', chunkSize,
+ print 'lineLength:', lineLength,
+ print 'numLines:', numLines,
+ print 'CPU Time: ', after - before
+
+
+
+def main():
+ for numLines in 100, 1000:
+ for lineLength in (10, 100, 1000):
+ for chunkSize in (1, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+ for numLines in 10000, 50000:
+ for lineLength in (1000, 2000):
+ for chunkSize in (51, 500, 5000):
+ benchmark(chunkSize, lineLength, numLines)
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/netstringreceiver.py b/doc/core/benchmarks/netstringreceiver.py
new file mode 100644
index 0000000..e48f66e
--- /dev/null
+++ b/doc/core/benchmarks/netstringreceiver.py
@@ -0,0 +1,242 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.test import proto_helpers, test_protocols
+import math
+import time
+import sys
+import os
+import gc
+
+NETSTRING_PREFIX_TEMPLATE ="%d:"
+NETSTRING_POSTFIX = ","
+USAGE = """\
+Usage: %s
+
+This script creates up to 2 ** chunks with up to 2 **
+ characters and sends them to the NetstringReceiver. The
+sorted performance data for all combination is written to
+afterwards.
+
+You might want to start with a small number, maybe 10 or 12, and slowly
+increase it. Stop when the performance starts to deteriorate ;-).
+"""
+
+class PerformanceTester(object):
+ """
+ A class for testing the performance of some
+ """
+
+ headers = []
+ lineFormat = ""
+ performanceData = {}
+
+ def __init__(self, filename):
+ """
+ Initializes C{self.filename}.
+
+ If a file with this name already exists, asks if it should be
+ overwritten. Terminates with exit status 1, if the user does
+ not accept.
+ """
+ if os.path.isfile(filename):
+ response = raw_input(("A file named %s exists. "
+ "Overwrite it (y/n)? ") % filename)
+ if response.lower() != "y":
+ print "Performance test cancelled."
+ sys.exit(1)
+ self.filename = filename
+
+
+ def testPerformance(self, number):
+ """
+ Drives the execution of C{performTest} with arguments between
+ 0 and C{number - 1}.
+
+ @param number: Defines the number of test runs to be performed.
+ @type number: C{int}
+ """
+ for iteration in xrange(number):
+ self.performTest(iteration)
+
+
+ def performTest(self, iteration):
+ """
+ Performs one test iteration. Overwrite this.
+
+ @param iteration: The iteration number. Can be used to configure
+ the test.
+ @type iteration: C{int}
+ @raise NotImplementedError: because this method has to be implemented
+ by the subclass.
+ """
+ raise NotImplementedError
+
+
+ def createReport(self):
+ """
+ Creates a file and writes a table with performance data.
+
+ The performance data are ordered by the total size of the netstrings.
+ In addition they show the chunk size, the number of chunks and the
+ time (in seconds) that elapsed while the C{NetstringReceiver}
+ received the netstring.
+
+ @param filename: The name of the report file that will be written.
+ @type filename: C{str}
+ """
+ self.outputFile = open(self.filename, "w")
+ self.writeHeader()
+ self.writePerformanceData()
+ self.writeLineSeparator()
+ print "The report was written to %s." % self.filename
+
+
+ def writeHeader(self):
+ """
+ Writes the table header for the report.
+ """
+ self.writeLineSeparator()
+ self.outputFile.write("| %s |\n" % (" | ".join(self.headers),))
+ self.writeLineSeparator()
+
+
+ def writeLineSeparator(self):
+ """
+ Writes a 'line separator' made from '+' and '-' characters.
+ """
+ dashes = ("-" * (len(header) + 2) for header in self.headers)
+ self.outputFile.write("+%s+\n" % "+".join(dashes))
+
+
+ def writePerformanceData(self):
+ """
+ Writes one line for each item in C{self.performanceData}.
+ """
+ for combination, elapsed in sorted(self.performanceData.iteritems()):
+ totalSize, chunkSize, numberOfChunks = combination
+ self.outputFile.write(self.lineFormat %
+ (totalSize, chunkSize, numberOfChunks,
+ elapsed))
+
+
+
+class NetstringPerformanceTester(PerformanceTester):
+ """
+ A class for determining the C{NetstringReceiver.dataReceived} performance.
+
+ Instantiates a C{NetstringReceiver} and calls its
+ C{dataReceived()} method with different chunks sizes and numbers
+ of chunks. Presents a table showing the relation between input
+ data and time to process them.
+ """
+
+ headers = ["Chunk size", "Number of chunks", "Total size",
+ "Time to receive" ]
+ lineFormat = ("| %%%dd | %%%dd | %%%dd | %%%d.4f |\n" %
+ tuple([len(header) for header in headers]))
+
+ def __init__(self, filename):
+ """
+ Sets up the output file and the netstring receiver that will be
+ used for receiving data.
+
+ @param filename: The name of the file for storing the report.
+ @type filename: C{str}
+ """
+ PerformanceTester.__init__(self, filename)
+ transport = proto_helpers.StringTransport()
+ self.netstringReceiver = test_protocols.TestNetstring()
+ self.netstringReceiver.makeConnection(transport)
+
+
+ def performTest(self, number):
+ """
+ Tests the performance of C{NetstringReceiver.dataReceived}.
+
+ Feeds netstrings of various sizes in different chunk sizes
+ to a C{NetstringReceiver} and stores the elapsed time in
+ C{self.performanceData}.
+
+ @param number: The maximal chunks size / number of
+ chunks to be checked.
+ @type number: C{int}
+ """
+ chunkSize = 2 ** number
+ numberOfChunks = chunkSize
+ while numberOfChunks:
+ self.testCombination(chunkSize, numberOfChunks)
+ numberOfChunks = numberOfChunks // 2
+
+
+ def testCombination(self, chunkSize, numberOfChunks):
+ """
+ Tests one combination of chunk size and number of chunks.
+
+ @param chunkSize: The size of one chunk to be sent to the
+ C{NetstringReceiver}.
+ @type chunkSize: C{int}
+ @param numberOfChunks: The number of C{chunkSize}-sized chunks to
+ be sent to the C{NetstringReceiver}.
+ @type numberOfChunks: C{int}
+ """
+ chunk, dataSize = self.configureCombination(chunkSize, numberOfChunks)
+ elapsed = self.receiveData(chunk, numberOfChunks, dataSize)
+ key = (chunkSize, numberOfChunks, dataSize)
+ self.performanceData[key] = elapsed
+
+
+ def configureCombination(self, chunkSize, numberOfChunks):
+ """
+ Updates C{MAX_LENGTH} for {self.netstringReceiver} (to avoid
+ C{NetstringParseErrors} that might be raised if the size
+ exceeds the default C{MAX_LENGTH}).
+
+ Calculates and returns one 'chunk' of data and the total size
+ of the netstring.
+
+ @param chunkSize: The size of chunks that will be received.
+ @type chunkSize: C{int}
+ @param numberOfChunks: The number of C{chunkSize}-sized chunks
+ that will be received.
+ @type numberOfChunks: C{int}
+
+ @return: A tuple consisting of string of C{chunkSize} 'a'
+ characters and the size of the netstring data portion.
+ """
+ chunk = "a" * chunkSize
+ dataSize = chunkSize * numberOfChunks
+ self.netstringReceiver.MAX_LENGTH = dataSize
+ numberOfDigits = math.ceil(math.log10(dataSize)) + 1
+ return chunk, dataSize
+
+
+ def receiveData(self, chunk, numberOfChunks, dataSize):
+ dr = self.netstringReceiver.dataReceived
+ now = time.time()
+ dr(NETSTRING_PREFIX_TEMPLATE % (dataSize,))
+ for idx in xrange(numberOfChunks):
+ dr(chunk)
+ dr(NETSTRING_POSTFIX)
+ elapsed = time.time() - now
+ assert self.netstringReceiver.received, "Didn't receive string!"
+ return elapsed
+
+
+def disableGarbageCollector():
+ gc.disable()
+ print 'Disabled Garbage Collector.'
+
+
+def main(number, filename):
+ disableGarbageCollector()
+ npt = NetstringPerformanceTester(filename)
+ npt.testPerformance(int(number))
+ npt.createReport()
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ print USAGE % sys.argv[0]
+ sys.exit(1)
+ main(*sys.argv[1:3])
diff --git a/doc/core/benchmarks/task.py b/doc/core/benchmarks/task.py
new file mode 100644
index 0000000..e3d437b
--- /dev/null
+++ b/doc/core/benchmarks/task.py
@@ -0,0 +1,26 @@
+
+"""
+Benchmarks for L{twisted.internet.task}.
+"""
+
+from timer import timeit
+
+from twisted.internet import task
+
+def test_performance():
+ """
+ L{LoopingCall} should not take long to skip a lot of iterations.
+ """
+ clock = task.Clock()
+ call = task.LoopingCall(lambda: None)
+ call.clock = clock
+
+ call.start(0.1)
+ clock.advance(1000000)
+
+
+def main():
+ print "LoopingCall large advance takes", timeit(test_performance, iter=1)
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/timer.py b/doc/core/benchmarks/timer.py
new file mode 100644
index 0000000..78181c6
--- /dev/null
+++ b/doc/core/benchmarks/timer.py
@@ -0,0 +1,24 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Helper stuff for benchmarks.
+"""
+
+import gc
+gc.disable()
+print 'Disabled GC'
+
+def timeit(func, iter = 1000, *args, **kwargs):
+ """
+ timeit(func, iter = 1000 *args, **kwargs) -> elapsed time
+
+ calls func iter times with args and kwargs, returns time elapsed
+ """
+
+ from time import time as currentTime
+ r = range(iter)
+ t = currentTime()
+ for i in r:
+ func(*args, **kwargs)
+ return currentTime() - t
diff --git a/doc/core/benchmarks/tpclient.py b/doc/core/benchmarks/tpclient.py
new file mode 100644
index 0000000..9e5e082
--- /dev/null
+++ b/doc/core/benchmarks/tpclient.py
@@ -0,0 +1,60 @@
+"""Throughput test."""
+
+import time, sys
+from twisted.internet import reactor, protocol
+from twisted.python import log
+
+TIMES = 10000
+S = "0123456789" * 1240
+
+toReceive = len(S) * TIMES
+
+class Sender(protocol.Protocol):
+
+ def connectionMade(self):
+ start()
+ self.numSent = 0
+ self.received = 0
+ self.transport.registerProducer(self, 0)
+
+ def stopProducing(self):
+ pass
+
+ def pauseProducing(self):
+ pass
+
+ def resumeProducing(self):
+ self.numSent += 1
+ self.transport.write(S)
+ if self.numSent == TIMES:
+ self.transport.unregisterProducer()
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ shutdown(self.numSent == TIMES)
+
+
+started = None
+
+def start():
+ global started
+ started = time.time()
+
+def shutdown(success):
+ if not success:
+ raise SystemExit, "failure or something"
+ passed = time.time() - started
+ print "Throughput (send): %s kbytes/sec" % ((toReceive / passed) / 1024)
+ reactor.stop()
+
+
+def main():
+ f = protocol.ClientFactory()
+ f.protocol = Sender
+ reactor.connectTCP(sys.argv[1], int(sys.argv[2]), f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ #log.startLogging(sys.stdout)
+ main()
diff --git a/doc/core/benchmarks/tpclient_nt.py b/doc/core/benchmarks/tpclient_nt.py
new file mode 100644
index 0000000..a8170d7
--- /dev/null
+++ b/doc/core/benchmarks/tpclient_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput client."""
+
+import socket, time, sys
+
+TIMES = 50000
+S = "0123456789" * 1024
+sent = len(S) * TIMES
+
+def main():
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((sys.argv[1], int(sys.argv[2])))
+ start = time.time()
+ i = 0
+ while i < TIMES:
+ i += 1
+ s.sendall(S)
+ passed = time.time() - start
+ print "Throughput: %s kbytes/sec" % ((sent / passed) / 1024)
+ s.close()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/benchmarks/tpserver.py b/doc/core/benchmarks/tpserver.py
new file mode 100644
index 0000000..49024e1
--- /dev/null
+++ b/doc/core/benchmarks/tpserver.py
@@ -0,0 +1,19 @@
+"""Throughput server."""
+
+import sys
+
+from twisted.protocols.wire import Discard
+from twisted.internet import protocol, reactor
+from twisted.python import log
+
+
+def main():
+ f = protocol.ServerFactory()
+ f.protocol = Discard
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/doc/core/benchmarks/tpserver_nt.py b/doc/core/benchmarks/tpserver_nt.py
new file mode 100644
index 0000000..e4bfdda
--- /dev/null
+++ b/doc/core/benchmarks/tpserver_nt.py
@@ -0,0 +1,22 @@
+"""Non-twisted throughput server."""
+
+import socket, signal, sys
+
+def signalhandler(*args):
+ print "alarm!"
+ sys.stdout.flush()
+
+signal.signal(signal.SIGALRM, signalhandler)
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.bind(('', 8001))
+s.listen(1)
+while 1:
+ c, (h, p) = s.accept()
+ c.settimeout(30)
+ signal.alarm(5)
+ while 1:
+ d = c.recv(16384)
+ if not d:
+ break
+ c.close()
diff --git a/doc/core/development/index.html b/doc/core/development/index.html
new file mode 100644
index 0000000..183d76b
--- /dev/null
+++ b/doc/core/development/index.html
@@ -0,0 +1,26 @@
+
+
+Twisted Documentation: Development of Twisted
+
+
+
+
+ Development of Twisted
+
+
+
+
+
This documentation is for people who work on the Twisted codebase itself,
+rather than for people who want to use Twisted in their own projects.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/listings/new_module_template.py b/doc/core/development/listings/new_module_template.py
new file mode 100644
index 0000000..85cf04b
--- /dev/null
+++ b/doc/core/development/listings/new_module_template.py
@@ -0,0 +1,12 @@
+# -*- test-case-name: -*-
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Docstring goes here.
+"""
+
+
+__all__ = []
diff --git a/doc/core/development/naming.html b/doc/core/development/naming.html
new file mode 100644
index 0000000..1f139dd
--- /dev/null
+++ b/doc/core/development/naming.html
@@ -0,0 +1,38 @@
+
+
+Twisted Documentation: Naming Conventions
+
+
+
+
+ Naming Conventions
+
+
+
+
+
+
While this may sound like a small detail, clear method naming is important to provide an API that developers familiar with event-based programming can pick up quickly.
+
+
Since the idea of a method call maps very neatly onto that of a received event, all event handlers are simply methods named after past-tense verbs. All class names are descriptive nouns, designed to mirror the is-a relationship of the abstractions they implement. All requests for notification or transmission are present-tense imperative verbs.
+
+
Here are some examples of this naming scheme:
+
+
+An event notification of data received from peer:
+dataReceived(data)
+A request to send data: write(data)
+A class that implements a protocol: Protocol
+
+
+
The naming is platform neutral. This means that the names are equally appropriate in a wide variety of environments, as long as they can publish the required events.
+
+
It is self-consistent. Things that deal with TCP use the acronym TCP, and it is always capitalized. Dropping, losing, terminating, and closing the connection are all referred to as losing the connection. This symmetrical naming allows developers to easily locate other API calls if they have learned a few related to what they want to do.
+
+
It is semantically clear. The semantics of dataReceived are simple: there are some bytes available for processing. This remains true even if the lower-level machinery to get the data is highly complex.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/philosophy.html b/doc/core/development/philosophy.html
new file mode 100644
index 0000000..0dd2ebc
--- /dev/null
+++ b/doc/core/development/philosophy.html
@@ -0,0 +1,58 @@
+
+
+Twisted Documentation: Philosophy
+
+
+
+
+ Philosophy
+
+
+
+
+
+
Abstraction Levels
+
+
When implementing interfaces to the operating system or
+the network, provide two interfaces:
+
+
+One that doesn't hide platform specific or library specific
+functionality.
+For example, you can use file descriptors on Unix, and Win32 events on
+Windows.
+
+One that provides a high level interface hiding platform specific
+details.
+E.g. process running uses same API on Unix and Windows, although
+the implementation is very different.
+
+
+
+
Restated in a more general way:
+
+
+Provide all low level functionality for your specific domain,
+without limiting the policies and decisions the user can make.
+Provide a high level abstraction on top of the low level
+implementation (or implementations) which implements the
+common use cases and functionality that is used in most cases.
+
+
+
Learning Curves
+
+
Require the minimal amount of work and learning on part of the
+user to get started. If this means they have less functionality,
+that's OK, when they need it they can learn a bit more. This
+will also lead to a cleaner, easier to test design.
+
+
For example - using twistd is a great way to deploy applications.
+But to get started you don't need to know about it. Later on you can
+start using twistd, but its usage is optional.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/coding-standard.html b/doc/core/development/policy/coding-standard.html
new file mode 100644
index 0000000..c83ae77
--- /dev/null
+++ b/doc/core/development/policy/coding-standard.html
@@ -0,0 +1,818 @@
+
+
+Twisted Documentation: Twisted Coding Standard
+
+
+
+
+ Twisted Coding Standard
+
+
+
+
+
Naming
+
+
Try to choose names which are both easy to remember and
+ meaningful. Some silliness is OK at the module naming level
+ (see twisted.spread
...) but when
+ choosing class names, be as precise as possible.
+
+
Try to avoid overloaded terms. This rule is often broken,
+ since it is incredibly difficult, as most normal words have
+ already been taken by some other software. More importantly,
+ try to avoid meaningless words. In particular, words like
+ handler , processor , engine , manager
+ and component don't really indicate what something does,
+ only that it does something .
+
+
Use American spelling in both names and docstrings. For compound
+ technical terms such as 'filesystem', use a non-hyphenated spelling in
+ both docstrings and code in order to avoid unnecessary
+ capitalization.
+
+
Testing
+
+
Overview
+
+
Twisted development should always be
+
+ test-driven . The complete test suite in the head of the SVN trunk is required to
+ be passing on
+ supported platforms at all times. Regressions in the test suite
+ are addressed by reverting whatever revisions introduced them. For
+ complete documentation about testing Twisted itself, refer to the
+ Test Standard . What follows is
+ intended to be a synopsis of the most important points.
+
+
Test Suite
+
+
The Twisted test suite is spread across many subpackages of the
+ twisted
package. Many older tests are in
+ twisted.test
. Others can be found at places such as
+ twisted.web.test
(for twisted.web
tests)
+ or twisted.internet.test
(for twisted.internet
+ tests). The latter arrangement, twisted.somepackage.test
,
+ is preferred for new tests except when a test module already exists in
+ twisted.test
.
+
+
+
+ Parts of the Twisted test suite may serve as good examples of how to
+ write tests for Twisted or for Twisted-based libraries (newer parts of
+ the test suite are generally better examples than older parts - check
+ when the code you are looking at was written before you use it as an
+ example of what you should write). The names of test modules should
+ begin with test_
so that they are automatically discoverable by
+ test runners such as Trial. Twisted's unit tests are written using
+ twisted.trial
, an xUnit library which has been
+ extensively customized for use in testing Twisted and Twisted-based
+ libraries.
+
+
Implementation (ie, non-test) source files should begin with a
+ test-case-name
tag which gives the name of any test
+ modules or packages which exercise them. This lets tools discover a
+ subset of the entire test suite which they can run first to find tests
+ which might be broken by a particular change.
+
+
It is strongly suggested that developers learn to use Emacs, and use
+ the twisted-dev.el
file included in
+ twisted-emacs
+ to bind the F9 key to run unit tests and bang on it
+ frequently. Support for other editors is unavailable at this time but
+ we would love to provide it.
+
+
To run the whole Twisted test without using emacs, use trial:
+
+
+$ bin/trial twisted
+
+
+
To run an individual test module, such as
+ twisted/mail/test/test_pop3.py
, specify the module
+ name:
+
+
+$ bin/trial twisted.mail.test.test_pop3
+
+
+
To run the tests associated with a particular implementation file,
+ such as twisted/mail/pop3.py
, use the
+ testmodule
option:
+
+
+$ bin/trial twisted/mail/pop3.py
+
+
+
All unit test methods should have docstrings specifying at a high
+ level the intent of the test. That is, a description that users of the
+ method would understand.
+
+
If you modify, or write a new, HOWTO, please read the Lore
+ documentation to learn how to format the docs.
+
+
Copyright Header
+
+
Whenever a new file is added to the repository, add the following
+ license header at the top of the file:
+
+
1
+2
+
+
+
+
+
When you update existing files, if there is no copyright header, add
+ one.
+
+
Whitespace
+
+
Indentation is 4 spaces per indent. Tabs are not allowed. It
+ is preferred that every block appear on a new line, so that
+ control structure indentation is always visible.
+
+
Lines are flowed at 79 columns. They must not have trailing
+ whitespace. Long lines must be wrapped using implied line continuation
+ inside parentheses; backslashes aren't allowed. To handle long import
+ lines, please repeat the import like this:
+
+
1
+2
+
from very .long .package import foo , bar , baz
+from very .long .package import qux , quux , quuux
+
+
+
Top-level classes and functions must be separated with 3 blank lines,
+ and class-level functions with 2 blank lines. The control-L (i.e. ^L) form
+ feed character must not be used.
+
+
Modules
+
+
Modules must be named in all lower-case, preferably short,
+ single words. If a module name contains multiple words, they
+ may be separated by underscores or not separated at all.
+
+
Modules must have a copyright message, a docstring and a
+ reference to a test module that contains the bulk of its tests.
+ Use this template:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
+
+
+
+
+
+"""
+Docstring goes here.
+"""
+
+
+__all__ = []
+
+
+
In most cases, modules should contain more than one class,
+ function, or method; if a module contains only one object,
+ consider refactoring to include more related functionality in
+ that module.
+
+
Depending on the situation, it is acceptable to have imports that
+ look like this:
+
1
+
from twisted .internet .defer import Deferred
+
+ or like this:
+
1
+
from twisted .internet import defer
+
+ That is, modules should import
modules or
classes and
+ functions , but not
packages .
+
+
Wildcard import syntax may not be used by code in Twisted. These
+ imports lead to code which is difficult to read and maintain by
+ introducing complexity which strains human readers and automated tools
+ alike. If you find yourself with many imports to make from a single
+ module and wish to save typing, consider importing the module itself,
+ rather than its attributes.
+
+
Relative imports (or sibling imports ) may not be
+ used by code in Twisted. Relative imports allow certain circularities
+ to be introduced which can ultimately lead to unimportable modules or
+ duplicate instances of a single module. Relative imports also make the
+ task of refactoring more difficult.
+
+
In case of local names conflicts due to import, use the as
+ syntax, for example:
+
1
+
from twisted .trial import util as trial_util
+
+
+
The encoding must always be ASCII, so no coding cookie is necessary.
+
+
Packages
+
+
Package names should follow the same conventions as module
+ names. All modules must be encapsulated in some package. Nested
+ packages may be used to further organize related modules.
+
+
__init__.py
must never contain anything other than a
+ docstring and (optionally) an __all__
attribute. Packages are
+ not modules and should be treated differently. This rule may be
+ broken to preserve backwards compatibility if a module is made
+ into a nested package as part of a refactoring.
+
+
If you wish to promote code from a module to a package, for
+ example, to break a large module out into several smaller
+ files, the accepted way to do this is to promote from within
+ the module. For example,
+
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
+
+import child
+
+
+import parent
+class Foo :
+ pass
+parent .Foo = Foo
+
+
+
Every package should be added to the list in
+ setup.py
.
+
+
Packages must not depend circularly upon each other. To simplify
+ maintaining this state, packages must also not import each other
+ circularly. While this applies to all packages within Twisted, one
+ twisted.python
deserves particular attention, as it may
+ not depend on any other Twisted package.
+
+
String Formatting Operations
+
+
When using string formatting
+ operations like formatString % values
you should always
+ use a tuple if you're using non-mapping values
. This is to
+ avoid unexpected behavior when you think you're passing in a single value,
+ but the value is unexpectedly a tuple, e.g.:
+
+
1
+2
+
def foo (x ):
+ return "Hi %s\n" % x
+
+
+
The example shows you can pass in foo("foo")
or
+ foo(3)
fine, but if you pass in foo((1,2))
,
+ it raises a TypeError
. You should use this instead:
+
+
1
+2
+
def foo (x ):
+ return "Hi %s\n" % (x ,)
+
+
+
Docstrings
+
+
Docstrings should always be used to describe the
+ purpose of methods, functions, classes, and modules.
+
+
Docstrings are never to be used to provide semantic
+ information about an object; this rule may be violated if the
+ code in question is to be used in a system where this is a
+ requirement (such as Zope).
+
+
Docstrings should be indented to the level of the code they
+ are documenting.
+
+
Docstrings should be triple-quoted. The opening and the closing of the
+ docstrings should be on a line by themselves. For example:
+
1
+2
+3
+4
+5
+6
+7
+8
+
class Ninja (object ):
+ """
+ A L{Ninja} is a warrior specializing in various unorthodox arts of war.
+ """
+ def attack (self , someone ):
+ """
+ Attack C{someone} with this L{Ninja}'s shuriken.
+ """
+
+
+
+
Docstrings should be written in epytext format; more
+ documentation is available in the
+ Epytext Markup Language documentation .
+
+
Additionally, to accommodate emacs users, single quotes of the type of
+ the docstring's triple-quote should be escaped. This will prevent font-lock from
+ accidentally fontifying large portions of the file as a string.
+
+
For example,
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
def foo2bar (f ):
+ """
+ Convert L{foo}s to L{bar}s.
+
+ A function that should be used when you have a C{foo} but you want a
+ C{bar}; note that this is a non-destructive operation. If this method
+ can't convert the C{foo} to a C{bar} it will raise a L{FooException}.
+
+ @param f: C{foo}
+ @type f: str
+
+ For example::
+
+ import wombat
+ def sample(something):
+ f = something.getFoo()
+ f.doFooThing()
+ b = wombat.foo2bar(f)
+ b.doBarThing()
+ return b
+
+ """
+
+
+
+
Comments
+
+
Comments marked with XXX or TODO must contain a reference to the
+ associated ticket.
+
+
Versioning
+
+
The API documentation should be marked up with version information.
+ When a new API is added the class should be marked with the epytext
+ @since:
field including the version number when
+ the change was introduced, eg. @since: 8.1
.
+
+
Scripts
+
+
For each script , that is, a program you expect a Twisted user
+ to run from the command-line, the following things must be done:
+
+
+ Write a module in twisted.scripts
+ which contains a callable global named run
. This
+ will be called by the command line part with no arguments (it
+ will usually read sys.argv
). Feel free to write more
+ functions or classes in this module, if you feel they are useful
+ to others.
+
+ Create a file which contains a shebang line for Python. For Twisted
+ Core, this file should be placed in the bin/
directory; for
+ example, bin/twistd
. For sub-projects, it should be placed
+ in bin/<subproject>
; for example, the key-generation tool
+ for the Conch sub-project is in bin/conch/ckeygen
.
+1
+
+
+
+ To make sure that the script is portable across different UNIX like
+ operating systems we use the /usr/bin/env
command. The env
+ command allows you to run a program in a modified environment. That way
+ you don't have to search for a program via the PATH
environment
+ variable. This makes the script more portable but note that it is not a
+ foolproof method. Always make sure that /usr/bin/env
exists or
+ use a softlink/symbolic link to point it to the correct path. Python's
+ distutils will rewrite the shebang line upon installation so this policy
+ only covers the source files in version control.
+
+ For core scripts, add this Twisted running-from-SVN header:
+1
+2
+3
+4
+5
+
import sys
+try :
+ import _preamble
+except ImportError :
+ sys .clear_exc ()
+
+
+ Or for sub-project scripts, add a modified version which also adjusts sys.path
:
+1
+2
+3
+4
+5
+6
+7
+8
+
import sys , os
+extra = os .path .dirname (os .path .dirname (sys .argv [0 ]))
+sys .path .insert (0 , extra )
+try :
+ import _preamble
+except ImportError :
+ sys .clear_exc ()
+sys .path .remove (extra )
+
+
+ And end with:
+1
+2
+
from twisted .scripts .yourmodule import run
+run ()
+
+
+ Write a manpage and add it to the man
folder
+ of a subproject's doc
folder. On Debian systems
+ you can find a skeleton example of a manpage in
+ /usr/share/doc/man-db/examples/manpage.example
.
+
+
+
This will insure your program will work correctly for users of SVN,
+ Windows releases and Debian packages.
+
+
Examples
+
+
For example scripts you expect a Twisted user
+ to run from the command-line, add this Python shebang line at the top
+ of the file:
+
1
+
+
+
+
Standard Library Extension Modules
+
+
When using the extension version of a module for which there is also
+ a Python version, place the import statement inside a try/except block,
+ and import the Python version if the import fails. This allows code to
+ work on platforms where the extension version is not available. For
+ example:
+
+
1
+2
+3
+4
+
try :
+ import cPickle as pickle
+except ImportError :
+ import pickle
+
+
+ Use the "as" syntax of the import statement as well, to set
+ the name of the extension module to the name of the Python module.
+
+
Some modules don't exist across all supported Python versions. For
+ example, Python 2.3's sets
module was deprecated in Python 2.6
+ in favor of the set
and frozenset
builtins. When
+ you need to use sets or frozensets in your code, please use
+ the set
and frozenset
provided
+ by twisted.python.compat
. There are some
+ differences between sets.Set
and set
, that are
+ explained in the set
+ PEP . Please be sure to not rely on the behavior of one or the other
+ implementation.
+
+
Classes
+
+
Classes are to be named in mixed case, with the first letter
+ capitalized; each word separated by having its first letter
+ capitalized. Acronyms should be capitalized in their entirety.
+ Class names should not be prefixed with the name of the module they are
+ in. Examples of classes meeting this criteria:
+
+
+ twisted.spread.pb.ViewPoint
+ twisted.parser.patterns.Pattern
+
+
+
Examples of classes not meeting this criteria:
+
+
+ event.EventHandler
+ main.MainGadget
+
+
+
An effort should be made to prevent class names from clashing
+ with each other between modules, to reduce the need for
+ qualification when importing. For example, a Service subclass
+ for Forums might be named twisted.forum.service.ForumService,
+ and a Service subclass for Words might be
+ twisted.words.service.WordsService. Since neither of these
+ modules are volatile (see above) the classes may be
+ imported directly into the user's namespace and not cause
+ confusion.
+
+
New-style Classes
+
+
Classes and instances in Python come in two flavors: old-style or
+ classic, and new-style. Up to Python 2.1, old-style classes were the
+ only flavour available to the user, new-style classes were introduced
+ in Python 2.2 to unify classes and types. All classes added to Twisted
+ should be written as new-style classes. If x
+ is an instance of a new-style class, then type(x)
+ is the same as x.__class__
.
+
+
Methods
+
+
Methods should be in mixed case, with the first letter lower
+ case, each word separated by having its first letter
+ capitalized. For example, someMethodName
,
+ method
.
+
+
Sometimes, a class will dispatch to a specialized sort of
+ method using its name; for example, twisted.reflect.Accessor.
+ In those cases, the type of method should be a prefix in all
+ lower-case with a trailing underscore, so method names will
+ have an underscore in them. For example, get_someAttribute
.
+ Underscores in method names in twisted code are therefore
+ expected to have some semantic associated with them.
+
+
Some methods, in particular addCallback
and its
+ cousins return self to allow for chaining calls. In this case,
+ wrap the chain in parenthesis, and start each chained call on
+ a separate line, for example:
+
+
1
+2
+3
+4
+
return (foo ()
+ .addCallback (bar )
+ .addCallback (thud )
+ .addCallback (wozers ))
+
+
+
Callback Arguments
+
+
There are several methods whose purpose is to help the user set up
+ callback functions, for example Deferred.addCallback
or the
+ reactor's callLater
method. To make
+ access to the callback as transparent as possible, most of these methods
+ use **kwargs
to capture arbitrary arguments
+ that are destined for the user's callback. This allows the call to the
+ setup function to look very much like the eventual call to the target
+ callback function.
+
+
In these methods, take care to not have other argument names that will
+ steal the user's callback's arguments. When sensible, prefix these
+ internal argument names with an underscore. For example, RemoteReference.callRemote
is
+ meant to be called like this:
+
+
1
+2
+3
+4
+5
+
myref .callRemote ("addUser" , "bob" , "555-1212" )
+
+
+def addUser (name , phone ):
+ ...
+
+
+
where addUser is the remote method name. The user might also
+ choose to call it with named parameters like this:
+
+
1
+
myref .callRemote ("addUser" , name ="bob" , phone ="555-1212" )
+
+
+
In this case, callRemote
(and any code that uses the
+ **kwargs
syntax) must be careful to not use
+ name , phone , or any other name that might overlap with
+ a user-provided named parameter. Therefore, callRemote
is
+ implemented with the following signature:
+
+
1
+2
+3
+
class SomeClass (object ):
+ def callRemote (self , _name , *args , **kw ):
+ ...
+
+
+
Do whatever you can to reduce user confusion. It may also be
+ appropriate to assert
that the kwargs
+ dictionary does not contain parameters with names that will eventually
+ cause problems.
+
+
+
Special Methods
+
+
The augmented assignment protocol, defined by __iadd__
and other
+ similarly named methods, can be used to allow objects to be modified in
+ place or to rebind names if an object is immutable -- both through use
+ of the same operator. This can lead to confusing code, which in turn
+ leads to buggy code. For this reason, methods of the augmented
+ assignment protocol should not be used in Twisted.
+
+
Functions
+
+
Functions should be named similiarly to methods.
+
+
Functions or methods which are responding to events to
+ complete a callback or errback should be named _cbMethodName
or
+ _ebMethodName
, in order to distinguish them from normal
+ methods.
+
+
Attributes
+
+
Attributes should be named similarly to functions and
+ methods. Attributes should be named descriptively; attribute
+ names like mode
, type
, and
+ buf
are generally discouraged. Instead, use
+ displayMode
, playerType
, or
+ inputBuffer
.
+
+
Do not use Python's private attribute syntax; prefix
+ non-public attributes with a single leading underscore. Since
+ several classes have the same name in Twisted, and they are
+ distinguished by which package they come from, Python's
+ double-underscore name mangling will not work reliably in some
+ cases. Also, name-mangled private variables are more difficult
+ to address when unit testing or persisting a class.
+
+
An attribute (or function, method or class) should be
+ considered private when one or more of the following conditions
+ are true:
+
+
+ The attribute represents intermediate state which is not
+ always kept up-to-date.
+
+ Referring to the contents of the attribute or otherwise
+ maintaining a reference to it may cause resources to
+ leak.
+
+ Assigning to the attribute will break internal
+ assumptions.
+
+ The attribute is part of a known-to-be-sub-optimal
+ interface and will certainly be removed in a future
+ release.
+
+
+
+
Database
+
+
Database tables will be named with plural nouns.
+
+
Database columns will be named with underscores between
+ words, all lower case, since most databases do not distinguish
+ between case.
+
+
Any attribute, method argument, or method name that
+ corresponds directly to a column in the database will
+ be named exactly the same as that column, regardless of other
+ coding conventions surrounding that circumstance.
+
+
All SQL keywords should be in upper case.
+
+
C Code
+
+
Wherever possible, C code should be optional, and the
+ default python implementation should be maintained in tandem
+ with it. C code should be strict ANSI C, and
+ must build using GCC as well as Visual Studio
+ for Windows, and really shouldn't have any problems with other
+ compilers either. Don't do anything tricky.
+
+
C code should only be used for efficiency, not for binding
+ to external libraries. If your particular code is not
+ frequently run, write it in Python. If you require the use of
+ an external library, develop a separate, external bindings
+ package and make your twisted code depend on it.
+
+
Commit Messages
+
+
The commit messages are being distributed in a myriad of ways. Because
+ of that, you need to observe a few simple rules when writing a commit
+ message.
+
+
The first line of the message is being used as both the subject of
+ the commit email and the announcement on #twisted. Therefore, it should
+ be short (aim for < 80 characters) and descriptive -- and must be
+ able to stand alone (it is best if it is a complete sentence). The rest
+ of the e-mail should be separated with hard line breaks into
+ short lines (< 70 characters). This is free-format, so you can do
+ whatever you like here.
+
+
Commit messages should be about what , not how : we can
+ get how from SVN diff. Explain reasons for commits, and what they
+ affect.
+
+
Each commit should be a single logical change, which is internally
+ consistent. If you can't summarize your changes in one short line, this
+ is probably a sign that they should be broken into multiple checkins.
+
+
Source Control
+
+
Twisted currently uses Subversion for source control. All
+ development should occur using branches; when a task is
+ considered complete another Twisted developer may review it and if no
+ problems are found, it may be merged into trunk. The Twisted wiki has a start .
+ Branches must be used for major development. Branches
+ should be managed using Combinator (but
+ if you can manage them in some other way without anyone noticing, knock
+ yourself out).
+
+
Certain features of Subversion should be avoided.
+
+
+
+
+ Do not set the svn:ignore
property on any
+ file or directory. What you wish to ignore, others may wish to examine.
+ What others may wish you ignore, you may wish you examine.
+ svn:ignore
will affect everyone who uses
+ the repository, and so it is not the right mechanism to express personal
+ preferences.
+
+ If you wish to ignore certain files use the
+ global-ignores
feature of
+ ~/.subversion/config
, for example:
+
+
+[miscellany]
+global-ignores = dropin.cache *.pyc *.pyo *.o *.lo *.la #*# .*.rej *.rej .*~
+
+
+
+
+
+
Fallback
+
+
In case of conventions not enforced in this document, the reference
+ documents to use in fallback is
+ PEP 8 for Python
+ code and PEP 7 for
+ C code. For example, the paragraph Whitespace in Expressions and
+ Statements in PEP 8 describes what should be done in Twisted
+ code.
+
+
Recommendations
+
+
These things aren't necessarily standardizeable (in that
+ code can't be easily checked for compliance) but are a good
+ idea to keep in mind while working on Twisted.
+
+
If you're going to work on a fragment of the Twisted
+ codebase, please consider finding a way that you would use
+ such a fragment in daily life. Using a Twisted Web server on your
+ website encourages you to actively maintain and improve your code,
+ as the little everyday issues with using it become apparent.
+
+
Twisted is a big codebase! If you're
+ refactoring something, please make sure to recursively grep for
+ the names of functions you're changing. You may be surprised to
+ learn where something is called. Especially if you are moving
+ or renaming a function, class, method, or module, make sure
+ that it won't instantly break other code.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/doc-standard.html b/doc/core/development/policy/doc-standard.html
new file mode 100644
index 0000000..2eda492
--- /dev/null
+++ b/doc/core/development/policy/doc-standard.html
@@ -0,0 +1,196 @@
+
+
+Twisted Documentation: HTML Documentation Standard for Twisted
+
+
+
+
+ HTML Documentation Standard for Twisted
+
+
+
+
+
Allowable Tags
+
+
Please try to restrict your HTML usage to the following tags (all only for the original logical purpose, and not whatever visual effect you see): <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>
and <th>
.
+
+
Please avoid using the quote sign ("
) for quoting, and use the relevant html tags (<q></q>
) -- it is impossible to distinguish right and left quotes with the quote sign, and some more sophisticated output methods work better with that distinction.
+
+
Multi-line Code Snippets
+
+
Multi-line code snippets should be delimited with a
+ <pre> tag, with a mandatory class attribute. The
+ conventionalized classes are python , python-interpreter ,
+ and shell . For example:
+
+
python
+
Original markup:
+
+
+<p>
+For example, this is how one defines a Resource:
+</p>
+
+<pre class="python">
+from twisted.web import resource
+
+class MyResource(resource.Resource):
+ def render_GET(self, request):
+ return "Hello, world!"
+</pre>
+
+
+
+
Rendered result:
+
+ For example, this is how one defines a Resource:
+1
+2
+3
+4
+5
+
from twisted .web import resource
+
+class MyResource (resource .Resource ):
+ def render_GET (self , request ):
+ return "Hello, world!"
+
+
+
+
Note that you should never have leading indentation inside a
+ <pre> block -- this makes it hard for readers to
+ copy/paste the code.
+
+
python-interpreter
+
Original markup:
+
+
+<pre class="python-interpreter">
+>>> from twisted.web import resource
+>>> class MyResource(resource.Resource):
+... def render_GET(self, request):
+... return "Hello, world!"
+...
+>>> MyResource().render_GET(None)
+"Hello, world!"
+</pre>
+
+
+
+
Rendered result:
+
+
+>>> from twisted.web import resource
+>>> class MyResource(resource.Resource):
+... def render_GET(self, request):
+... return "Hello, world!"
+...
+>>> MyResource().render_GET(None)
+"Hello, world!"
+
+
+
+
shell
+
Original markup:
+
+
+ <pre class="shell">
+ $ twistd web --path /var/www
+ </pre>
+
+
+
+
Rendered result:
+
+
+$ twistd web --path /var/www
+
+
+
+
Code inside paragraph text
+
+
For single-line code-snippets and attribute, method, class,
+ and module names, use the <code> tag, with a class of
+ API or python . During processing, module or class-names
+ with class API will automatically be looked up in the API
+ reference and have a link placed around it referencing the
+ actual API documents for that module/classname. If you wish to
+ reference an API document, then make sure you at least have a
+ single module-name so that the processing code will be able to
+ figure out which module or class you're referring to.
+
+
You may also use the base
attribute in conjuction
+ with a class of API to indicate the module that should be prepended
+ to the module or classname. This is to help keep the documentation
+ clearer and less cluttered by allowing links to API docs that don't
+ need the module name.
+
Original markup:
+
+
+ <p>
+ To add a <code class="API">twisted.web.static.File</code>
+ instance to a <code class="API"
+ base="twisted.web.resource">Resource</code> instance, do
+ <code class="python">myResource.putChild("resourcePath",
+ File("/tmp"))</code>.
+ </p>
+
+
+
+
+
Rendered result:
+
+
+ To add a twisted.web.static.File
+ instance to a Resource
+ instance, do
+ myResource.putChild("resourcePath", File("/tmp"))
.
+
+
+
+
Headers
+
+
It goes without mentioning that you should use <hN> in
+ a sane way -- <h1> should only appear once in the
+ document, to specify the title. Sections of the document should
+ use <h2>, sub-headers <h3>, and so on.
+
+
XHTML
+
+
XHTML is mandatory. That means tags that don't have a
+ closing tag need a / ; for example, <hr />
+ . Also, tags which have optional closing tags in HTML
+ need to be closed in XHTML; for example,
+ <li>foo</li>
+
+
Tag Case
+
+
All tags will be done in lower-case. XHTML demands this, and
+ so do I. :-)
+
+
Footnotes
+
+
Footnotes are enclosed inside
+ <span class="footnote"></span>
. They must not
+ contain any markup.
+
+
Suggestions
+
+
Use lore -o lint
to check your documentation
+ is not broken. lore -o lint
will never change
+ your HTML, but it will complain if it doesn't like it.
+
+
Don't use tables for formatting. 'nuff said.
+
+
__all__
+
+
__all__
is a module level list of strings, naming
+ objects in the module that are public. Make sure publically exported classes,
+ functions and constants are listed here.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/index.html b/doc/core/development/policy/index.html
new file mode 100644
index 0000000..d733b2a
--- /dev/null
+++ b/doc/core/development/policy/index.html
@@ -0,0 +1,33 @@
+
+
+Twisted Documentation: Twisted Development Policy
+
+
+
+
+ Twisted Development Policy
+
+
+
+
+
+
+This series of documents is designed for people who wish to contribute to the
+Twisted codebase.
+
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/svn-dev.html b/doc/core/development/policy/svn-dev.html
new file mode 100644
index 0000000..1e6c74c
--- /dev/null
+++ b/doc/core/development/policy/svn-dev.html
@@ -0,0 +1,230 @@
+
+
+Twisted Documentation: Working from Twisted's Subversion repository
+
+
+
+
+ Working from Twisted's Subversion repository
+
+
+
+
+
If you're going to be doing development on Twisted itself, or if you want
+to take advantage of bleeding-edge features (or bug fixes) that are not yet
+available in a numbered release, you'll probably want to check out a tree from
+the Twisted Subversion repository. The Trunk is where all current development
+takes place.
+
+
This document lists some useful tips for working on this cutting
+edge.
+
+
Checkout
+
+
Subversion tutorials can be found elsewhere, see in particular
+the Subversion homepage . The
+relevant data you need to check out a copy of the Twisted tree is available on
+the development
+page , and is as follows:
+
+
+$ svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk Twisted
+
+
+
Alternate tree names
+
+
By using svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk
+otherdir
, you can put the workspace tree in a directory other than
+Twisted . I do this (with a name like Twisted-Subversion ) to
+remind myself that this tree comes from Subversion and not from a released
+version (like Twisted-1.0.5 ). This practice can cause a few problems,
+because there are a few places in the Twisted tree that need to know where
+the tree starts, so they can add it to sys.path
without
+requiring the user manually set their PYTHONPATH. These functions walk the
+current directory up to the root, looking for a directory named
+Twisted (sometimes exactly that, sometimes with a
+.startswith
test). Generally these are test scripts or other
+administrative tools which expect to be launched from somewhere inside the
+tree (but not necessarily from the top).
+
+
If you rename the tree to something other than Twisted
, these
+tools may wind up trying to use Twisted source files from /usr/lib/python2.5
+or elsewhere on the default sys.path
. Normally this won't
+matter, but it is good to be aware of the issue in case you run into
+problems.
+
+
twisted/test/process_twisted.py
is one of these programs.
+
+
Combinator
+
+
In order to simplify the use of Subversion, we typically use
+Divmod Combinator .
+You may find it to be useful, too. In particular, because Twisted uses
+branches for almost all feature development, if you plan to contribute to
+Twisted you will probably find Combinator very useful. For more details,
+see the Combinator website, as well as the
+
+UQDS page.
+
+
Compiling C extensions
+
+
+There are currently several C extension modules in Twisted:
+twisted.internet.cfsupport
,
+twisted.internet.iocpreactor._iocp
,
+and twisted.python._epoll
. These modules
+are optional, but you'll have to compile them if you want to experience their
+features, performance improvements, or bugs. There are two approaches.
+
+
+
The first is to do a regular distutils ./setup.py build
, which
+will create a directory under build/
to hold both the generated
+.so
files as well as a copy of the 600-odd .py
files
+that make up Twisted. If you do this, you will need to set your PYTHONPATH to
+something like MyDir/Twisted/build/lib.linux-i686-2.5
in order to
+run code against the Subversion twisted (as opposed to whatever's installed in
+/usr/lib/python2.5
or wherever python usually looks). In
+addition, you will need to re-run the build
command every
+time you change a .py
file. The build/lib.foo
+directory is a copy of the main tree, and that copy is only updated when you
+re-run setup.py build
. It is easy to forget this and then wonder
+why your code changes aren't being expressed.
+
+
The second technique is to build the C modules in place, and point your
+PYTHONPATH at the top of the tree, like MyDir/Twisted
. This way
+you're using the .py files in place too, removing the confusion a forgotten
+rebuild could cause with the separate build/ directory above. To build the C
+modules in place, do ./setup.py build_ext -i
. You only need to
+re-run this command when you change the C files. Note that
+setup.py
is not Make, it does not always get the dependencies
+right (.h
files in particular), so if you are hacking on the
+cReactor you may need to manually delete the .o
files before
+doing a rebuild. Also note that doing a setup.py clean
will
+remove the .o
files but not the final .so
files,
+they must be deleted by hand.
+
+
+
Running tests
+
+
To run the full unit-test suite, do:
+
+
./bin/trial twisted
+
+
To run a single test file (like twisted/test/test_defer.py
),
+do one of:
+
+
./bin/trial twisted.test.test_defer
+
+
or
+
+
./bin/trial twisted/test/test_defer.py
+
+
To run any tests that are related to a code file, like
+twisted/protocols/imap4.py
, do:
+
+
./bin/trial --testmodule twisted/mail/imap4.py
+
+
This depends upon the .py
file having an appropriate
+test-case-name tag that indicates which test cases provide coverage.
+See the Test Standards document for
+details about using test-case-name . In this example, the
+twisted.mail.test.test_imap
test will be run.
+
+
Many tests create temporary files in /tmp or ./_trial_temp, but
+everything in /tmp should be deleted when the test finishes. Sometimes these
+cleanup calls are commented out by mistake, so if you see a stray
+/tmp/@12345.1
directory, it is probably from test_dirdbm
or test_popsicle
.
+Look for an rmtree
that has been commented out and complain to
+the last developer who touched that file.
+
+
Building docs
+
+
Twisted documentation (not including the automatically-generated API docs)
+is in Lore Format .
+These .xhtml
files are translated into .html
files by
+the bin/lore/lore script, which can check the files for syntax problems
+(hlint), process multiple files at once, insert the files into a template
+before processing, and can also translate the files into LaTeX or PostScript
+instead.
+
+
To build the HTML form of the howto/ docs, do the following. Note that
+the index file will be placed in doc/core/howto/index.html
.
+
+
+./bin/lore/lore -p --config template=doc/core/howto/template.tpl doc/core/howto/*.xhtml
+
+
+
To run hlint over a single Lore document, such as
+doc/development/policy/svn-dev.xhtml
, do the following. This is
+useful because the HTML conversion may bail without a useful explanation if
+it sees mismatched tags.
+
+
+./bin/lore/lore -n --output lint doc/development/policy/svn-dev.xhtml
+
+
+
To convert it to HTML (including markup, interpolation of examples,
+footnote processing, etc), do the following. The results will be placed in
+doc/development/policy/svn-dev.html
:
+
+
+./bin/lore/lore -p --config template=doc/core/howto/template.tpl \
+ doc/development/policy/svn-dev.xhtml
+
+
+
Note that hyperlinks to other documents may not be quite right unless you
+include a -l argument to bin/lore/lore
. Links in the
+.xhtml file are to .xhtml targets: when the .xhtml is turned into .html, the
+link targets are supposed to be turned into .html also. In addition to this,
+Lore markup of the form <code class="API">
is supposed to
+turn into a link to the corresponding API reference page. These links will
+probably be wrong unless the correct base URL is provided to Lore.
+
+
Committing and Post-commit Hooks
+
+
Twisted uses a customized
+
+trac-post-commit-hook to enable ticket updates based on svn commit
+logs. When making a branch for a ticket, the branch name should end
+in -<ticket number>
, for
+example my-branch-9999
. This will add a ticket comment containing a
+changeset link and branch name. To make your commit message show up as a comment
+on a Trac ticket, add a refs #<ticket number>
line at the
+bottom of your commit message. To automatically close a ticket on Trac
+as Fixed
and add a comment with the closing commit message, add
+a Fixes: #<ticket number>
line to your commit message. In
+general, a commit message closing a ticket looks like this:
+
+
+Merge my-branch-9999: A single-line summary.
+
+Author: jesstess
+Reviewers: exarkun, glyph
+Fixes: #9999
+
+My longer description of the changes made.
+
+
+
The Twisted Coding Standard
+elaborates on commit messages and source control.
+
+
Emacs
+
+
A minor mode for development with Twisted using Emacs is available. See
+twisted-dev.el
, provided by twisted-emacs ,
+for several utility functions which make it easier to grep for methods, run test cases, etc.
+
+
Building Debian packages
+
+
Our support for building Debian packages has fallen into disrepair. We
+would very much like to restore this functionality, but until we do so, if
+you are interested in this, you are on your own. See
+stdeb for one possible approach to
+this.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/test-standard.html b/doc/core/development/policy/test-standard.html
new file mode 100644
index 0000000..9539356
--- /dev/null
+++ b/doc/core/development/policy/test-standard.html
@@ -0,0 +1,399 @@
+
+
+Twisted Documentation: Unit Tests in Twisted
+
+
+
+
+ Unit Tests in Twisted
+
+
+
+
+
Each unit test tests one bit of functionality in the
+ software. Unit tests are entirely automated and complete quickly.
+ Unit tests for the entire system are gathered into one test suite,
+ and may all be run in a single batch. The result of a unit test
+ is simple: either it passes, or it doesn't. All this means you
+ can test the entire system at any time without inconvenience, and
+ quickly see what passes and what fails.
+
+
Unit Tests in the Twisted Philosophy
+
+
The Twisted development team adheres to the practice of Extreme Programming (XP),
+ and the usage of unit tests is a cornerstone XP practice. Unit tests are a
+ tool to give you increased confidence. You changed an algorithm -- did you
+ break something? Run the unit tests. If a test fails, you know where to
+ look, because each test covers only a small amount of code, and you know it
+ has something to do with the changes you just made. If all the tests pass,
+ you're good to go, and you don't need to second-guess yourself or worry that
+ you just accidentally broke someone else's program.
+
+
What to Test, What Not to Test
+
+
You don't have to write a test for every single
+ method you write, only production methods that could possibly break.
+
+
+
-- Kent Beck, Extreme Programming Explained , p. 58.
+
+
Running the Tests
+
+
How
+
+
From the root of the Twisted source tree, run
+ Trial :
+
+
+
+$ bin/trial twisted
+
+
+
You'll find that having something like this in your emacs init
+ files is quite handy:
+
+
+(defun runtests () (interactive)
+ (compile "python /somepath/Twisted/bin/trial /somepath/Twisted"))
+
+(global-set-key [(alt t)] 'runtests)
+
+
When
+
+
Always, always, always be sure all the
+ tests pass before committing any code. If someone else
+ checks out code at the start of a development session and finds
+ failing tests, they will not be happy and may decide to hunt
+ you down .
+
+
Since this is a geographically dispersed team, the person who can help
+ you get your code working probably isn't in the room with you. You may want
+ to share your work in progress over the network, but you want to leave the
+ main Subversion tree in good working order.
+ So use a branch ,
+ and merge your changes back in only after your problem is solved and all the
+ unit tests pass again.
+
+
Adding a Test
+
+
Please don't add new modules to Twisted without adding tests
+ for them too. Otherwise we could change something which breaks
+ your module and not find out until later, making it hard to know
+ exactly what the change that broke it was, or until after a
+ release, and nobody wants broken code in a release.
+
+
Tests go into dedicated test packages such as
+ twisted/test/
or twisted/conch/test/
,
+ and are named test_foo.py
, where foo
is the name
+ of the module or package being tested. Extensive documentation on using
+ the PyUnit framework for writing unit tests can be found in the
+ links section below.
+
+
+
One deviation from the standard PyUnit documentation: To ensure
+ that any variations in test results are due to variations in the
+ code or environment and not the test process itself, Twisted ships
+ with its own, compatible, testing framework. That just
+ means that when you import the unittest module, you will from twisted.trial import unittest
instead of the
+ standard import unittest
.
+
+
As long as you have followed the module naming and placement
+ conventions, trial
will be smart
+ enough to pick up any new tests you write.
+
+
PyUnit provides a large number of assertion methods to be used when
+ writing tests. Many of these are redundant. For consistency, Twisted
+ unit tests should use the assert
forms rather than the
+ fail
forms. Also, use assertEqual
,
+ assertNotEqual
, and assertAlmostEqual
rather
+ than assertEquals
, assertNotEquals
, and
+ assertAlmostEquals
. assertTrue
is also
+ preferred over assert_
. You may notice this convention is
+ not followed everywhere in the Twisted codebase. If you are changing
+ some test code and notice the wrong method being used in nearby code,
+ feel free to adjust it.
+
+
When you add a unit test, make sure all methods have docstrings
+ specifying at a high level the intent of the test. That is, a description
+ that users of the method would understand.
+
+
Test Implementation Guidelines
+
+
Here are some guidelines to follow when writing tests for the Twisted
+ test suite. Many tests predate these guidelines and so do not follow them.
+ When in doubt, follow the guidelines given here, not the example of old unit
+ tests.
+
+
Real I/O
+
+
Most unit tests should avoid performing real, platform-implemented I/O
+ operations. Real I/O is slow, unreliable, and unwieldy. When implementing
+ a protocol, twisted.test.proto_helpers.StringTransport
can be
+ used instead of a real TCP transport. StringTransport
is fast,
+ deterministic, and can easily be used to exercise all possible network
+ behaviors.
+
+
Real Time
+
+
Most unit tests should also avoid waiting for real time to pass. Unit
+ tests which construct and advance
+ a twisted.internet.task.Clock
are fast and
+ deterministic.
+
+
The Global Reactor
+
+
Since unit tests are avoiding real I/O and real time, they can usually
+ avoid using a real reactor. The only exceptions to this are unit tests for
+ a real reactor implementation. Unit tests for protocol implementations or
+ other application code should not use a reactor. Unit tests for real
+ reactor implementations should not use the global reactor, but should
+ instead use twisted.internet.test.reactormixins.ReactorBuilder
+ so they can be applied to all of the reactor implementations automatically.
+ In no case should new unit tests use the global reactor.
+
+
+
Skipping tests, TODO items
+
+
Trial, the Twisted unit test framework, has some extensions which are
+designed to encourage developers to add new tests. One common situation is
+that a test exercises some optional functionality: maybe it depends upon
+certain external libraries being available, maybe it only works on certain
+operating systems. The important common factor is that nobody considers
+these limitations to be a bug.
+
+
To make it easy to test as much as possible, some tests may be skipped in
+certain situations. Individual test cases can raise the
+SkipTest
exception to indicate that they should be skipped, and
+the remainder of the test is not run. In the summary (the very last thing
+printed, at the bottom of the test output) the test is counted as a
+skip instead of a success or fail . This should be used
+inside a conditional which looks for the necessary prerequisites:
+
+
1
+2
+3
+4
+5
+
class SSHClientTests (unittest .TestCase ):
+ def test_sshClient (self ):
+ if not ssh_path :
+ raise unittest .SkipTest ("cannot find ssh, nothing to test" )
+ foo ()
+
+
+
You can also set the .skip
attribute on the method, with a
+string to indicate why the test is being skipped. This is convenient for
+temporarily turning off a test case, but it can also be set conditionally (by
+manipulating the class attributes after they've been defined):
+
+
1
+2
+3
+4
+
class SomeThingTests (unittest .TestCase ):
+ def test_thing (self ):
+ dotest ()
+ test_thing .skip = "disabled locally"
+
+
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
class MyTestCase (unittest .TestCase ):
+ def test_one (self ):
+ ...
+ def test_thing (self ):
+ dotest ()
+
+if not haveThing :
+ MyTestCase .test_thing .im_func .skip = "cannot test without Thing"
+
+
+
+
Finally, you can turn off an entire TestCase at once by setting the .skip
+attribute on the class. If you organize your tests by the functionality they
+depend upon, this is a convenient way to disable just the tests which cannot
+be run.
+
+
1
+2
+3
+4
+5
+6
+7
+
class TCPTestCase (unittest .TestCase ):
+ ...
+class SSLTestCase (unittest .TestCase ):
+ if not haveSSL :
+ skip = "cannot test without SSL support"
+
+ ...
+
+
+
.todo and Testing New Functionality
+
+
Two good practices which arise from the XP development process are
+sometimes at odds with each other:
+
+
+ Unit tests are a good thing. Good developers recoil in horror when
+ they see a failing unit test. They should drop everything until the test
+ has been fixed.
+
+ Good developers write the unit tests first. Once tests are done, they
+ write implementation code until the unit tests pass. Then they stop.
+
+
+
These two goals will sometimes conflict. The unit tests that are written
+first, before any implementation has been done, are certain to fail. We want
+developers to commit their code frequently, for reliability and to improve
+coordination between multiple people working on the same problem together.
+While the code is being written, other developers (those not involved in the
+new feature) should not have to pay attention to failures in the new code.
+We should not dilute our well-indoctrinated Failing Test Horror Syndrome by
+crying wolf when an incomplete module has not yet started passing its unit
+tests. To do so would either teach the module author to put off writing or
+committing their unit tests until after all the functionality is
+working, or it would teach the other developers to ignore failing test
+cases. Both are bad things.
+
+
.todo is intended to solve this problem. When a developer first
+starts writing the unit tests for functionality that has not yet been
+implemented, they can set the .todo
attribute on the test
+methods that are expected to fail. These methods will still be run, but
+their failure will not be counted the same as normal failures: they will go
+into an expected failures category. Developers should learn to treat
+this category as a second-priority queue, behind actual test failures.
+
+
As the developer implements the feature, the tests will eventually start
+passing. This is surprising: after all those tests are marked as being
+expected to fail. The .todo tests which nevertheless pass are put into a
+unexpected success category. The developer should remove the .todo
+tag from these tests. At that point, they become normal tests, and their
+failure is once again cause for immediate action by the entire development
+team.
+
+
The life cycle of a test is thus:
+
+
+ Test is created, marked .todo
. Test fails: expected
+ failure .
+
+ Code is written, test starts to pass. unexpected success .
+
+ .todo
tag is removed. Test passes. success .
+
+ Code is broken, test starts to fail. failure . Developers spring
+ into action.
+
+ Code is fixed, test passes once more. success .
+
+
+
Any test which remains marked with .todo
for too long should
+be examined. Either it represents functionality which nobody is working on,
+or the test is broken in some fashion and needs to be fixed. Generally,
+.todo
may be of use while you are developing a feature, but
+by the time you are ready to commit anything, all the tests you have written
+should be passing. In other words, you should rarely, if ever, feel the need
+to add a test marked todo to trunk. When you do, consider whether a ticket
+in the issue tracker would be more useful.
+
+
Line Coverage Information
+
+
Trial provides line coverage information, which is very useful to ensure
+old code has decent coverage. Passing the --coverage
option to
+to Trial will generate the coverage information in a file called
+coverage
which can be found in the _trial_temp
+folder. This option requires Python 2.3.3 or newer.
+
+
Associating Test Cases With Source Files
+
+
Please add a test-case-name
tag to the source file that is
+covered by your new test. This is a comment at the beginning of the file
+which looks like one of the following:
+
+
1
+
+
+
+
or
+
+
1
+2
+
+
+
+
+
This format is understood by emacs to mark File Variables . The
+intention is to accept test-case-name
anywhere emacs would on
+the first or second line of the file (but not in the File
+Variables:
block that emacs accepts at the end of the file). If you
+need to define other emacs file variables, you can either put them in the
+File Variables:
block or use a semicolon-separated list of
+variable definitions:
+
+
1
+
+
+
+
If the code is exercised by multiple test cases, those may be marked by
+using a comma-separated list of tests, as follows: (NOTE: not all tools can
+handle this yet.. trial --testmodule
does, though)
+
+
1
+
+
+
+
The test-case-name
tag will allow trial
+--testmodule twisted/dir/myfile.py
to determine which test cases need
+to be run to exercise the code in myfile.py
. Several tools (as
+well as 's
+twisted-dev.el
's F9 command) use this to automatically
+run the right tests.
+
+
Links
+
+
+
+
See also Tips for writing tests for
+ Twisted code .
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/policy/writing-standard.html b/doc/core/development/policy/writing-standard.html
new file mode 100644
index 0000000..1bfaaa4
--- /dev/null
+++ b/doc/core/development/policy/writing-standard.html
@@ -0,0 +1,313 @@
+
+
+Twisted Documentation: Twisted Writing Standard
+
+
+
+
+ Twisted Writing Standard
+
+
+
+
+
The Twisted writing standard describes the documentation writing
+ styles we prefer in our documentation. This standard applies particularly
+ to howtos and other descriptive documentation.
+
+
This document should be read with the documentation standard , which describes
+ markup style for the documentation.
+
+
This document is meant to help Twisted documentation authors produce
+ documentation that does not have the following problems:
+
+
+ misleads users about what is good Twisted style;
+ misleads users into thinking that an advanced howto is an introduction
+ to writing their first Twisted server; and
+ misleads users about whether they fit the document's target audience:
+ for example, that they are able to use enterprise without knowing how to
+ write SQL queries.
+
+
+
General style
+
+
Documents should aim to be clear and concise, allowing the API
+ documentation and the example code to tell as much of the story as they
+ can. Demonstrations and where necessary supported arguments should always
+ preferred to simple statements ("here is how you would simplify this
+ code with Deferreds" rather than "Deferreds make code
+ simpler").
+
+
Documents should be clearly delineated into sections and subsections.
+ Each of these sections, like the overall document, should have a single
+ clear purpose. This is most easily tested by trying to have meaningful
+ headings: a section which is headed by "More details" or
+ "Advanced stuff" is not purposeful enough. There should be
+ fairly obvious ways to split a document. The two most common are task
+ based sectioning and sectioning which follows module and class
+ separations.
+
+
Documentation must use American English spelling, and where possible
+ avoid any local variants of either vocabulary or grammar. Grammatically
+ complex sentences should ideally be avoided: these make reading
+ unnecessarily difficult, particularly for non-native speakers.
+
+
Evangelism and usage documents
+
+
The Twisted documentation should maintain a reasonable distinction
+ between "evangelism" documentation, which compares the Twisted
+ design or Twisted best practice with other approaches and argues for the
+ Twisted approach, and "usage" documentation, which describes the
+ Twisted approach in detail without comparison to other possible
+ approaches.
+
+
While both kinds of documentation are useful, they have different
+ audiences. The first kind of document, evangelical documents, is useful to
+ a reader who is researching and comparing approaches and seeking to
+ understand the Twisted approach or Twisted functionality in order to
+ decide whether it is useful to them. The second kind of document, usage
+ documents, are useful to a reader who has decided to use Twisted and
+ simply wants further information about available functions and
+ architectures they can use to accomplish their goal.
+
+
Since they have distinct audiences, evangelism and detailed usage
+ documentation belongs in separate files. There should be links between
+ them in 'Further reading' or similar sections.
+
+
Descriptions of features
+
+
Descriptions of any feature added since release 2.0 of Twisted core
+ must have a note describing which release of which Twisted project they
+ were added in at the first mention in each document. If they are not yet
+ released, give them the number of the next minor release.
+
+
For example, a substantial change might have a version number added in
+ the introduction:
+
+
+ This document describes the Application infrastructure for deploying
+ Twisted applications (added in Twisted 1.3) .
+
+
+
The version does not need to be mentioned elsewhere in the document
+ except for specific features which were added in subsequent releases,
+ which might should be mentioned separately.
+
+
+ The simplest way to create a .tac
file, SuperTac (added
+ in Twisted Core 99.7) ...
+
+
In the case where the usage of a feature has substantially changed, the
+ number should be that of the release in which the current usage became
+ available. For example:
+
+
This document describes the Application infrastructure for
+ deploying Twisted applications (updated[/substantially updated] in Twisted
+ 2.7) .
+
+
Linking
+
+
The first occurrence of the name of any module, class or function should
+ always link to the API documents. Subsequent mentions may or may not link
+ at the author's discretion: discussions which are very closely bound to a
+ particular API should probably link in the first mention in the given
+ section.
+
+
Links between howtos are encouraged. Overview documents and tutorials
+ should always link to reference documents and in depth documents. These
+ documents should link among themselves wherever it's needed: if you're
+ tempted to re-describe the functionality of another module, you should
+ certainly link instead.
+
+
Introductions
+
+
The introductory section of a Twisted howto should immediately follow
+ the top-level heading and precede any subheadings.
+
+
The following items should be present in the introduction to Twisted
+ howtos: the introductory paragraph and the description of the target
+ audience.
+
+
Introductory paragraph
+
+
The introductory paragraph of a document should summarize what the
+ document is designed to present. It should use the both proper names for
+ the Twisted technologies and simple non-Twisted descriptions of the
+ technologies. For example, in this paragraph both the name of the technology
+ ("Conch") and a description ("SSH server") are used:
+
+
+ This document describes setting up a SSH server to serve data from the
+ file system using Conch, the Twisted SSH implementation.
+
+
+
The introductory paragraph should be relatively short, but should, like
+ the above, somewhere define the document's objective: what the reader
+ should be able to do using instructions in the document.
+
+
Description of target audience
+
+
Subsequent paragraphs in the introduction should describe the target
+ audience of the document: who would want to read it, and what they should
+ know before they can expect to use your document. For example:
+
+
+
+ The target audience of this document is a Twisted user who has a set of
+ filesystem like data objects that they would like to make available to
+ authenticated users over SFTP.
+
+
+
+ Following the directions in this document will require that you are
+ familiar with managing authentication via the Twisted Cred system.
+
+
+
+
Use your discretion about the extent to which you list assumed
+ knowledge. Very introductory documents that are going to be among a
+ reader's first exposure to Twisted will even need to specify that they
+ rely on knowledge of Python and of certain networking concepts (ports,
+ servers, clients, connections) but documents that are going to be sought
+ out by existing Twisted users for particular purposes only need to specify
+ other Twisted knowledge that is assumed.
+
+
Any knowledge of technologies that wouldn't be considered "core
+ Python" and/or "simple networking" need to be explicitly
+ specified, no matter how obvious they seem to someone familiar with the
+ technology. For example, it needs to be stated that someone using
+ enterprise should know SQL and should know how to set up and populate
+ databases for testing purposes.
+
+
Where possible, link to other documents that will fill in missing
+ knowledge for the reader. Linking to documents in the Twisted repository
+ is preferred but not essential.
+
+
Goals of document
+
+
The introduction should finish with a list of tasks that the user can
+ expect to see the document accomplish. These tasks should be concrete
+ rather than abstract, so rather than telling the user that they will
+ "understand Twisted Conch", you would list the specific tasks
+ that they will see the document do. For example:
+
+
+
+ This document will demonstrate the following tasks using Twisted Conch:
+
+
+
+ creating an anonymous access read-only SFTP server using a filesystem
+ backend;
+ creating an anonymous access read-only SFTP server using a proxy
+ backend connecting to an HTTP server; and
+ creating a anonymous access read and write SFTP server using a
+ filesystem backend.
+
+
+
+
In many cases this will essentially be a list of your code examples,
+ but it need not be. If large sections of your code are devoted to design
+ discussions, your goals might resemble the following:
+
+
+
+ This document will discuss the following design aspects of writing Conch
+ servers:
+
+
+
+ authentication of users; and
+ choice of data backends.
+
+
+
+
+
Example code
+
+
Wherever possible, example code should be provided to illustrate a
+ certain technique or piece of functionality.
+
+
Example code should try and meet as many of the following requirements
+ as possible:
+
+
+ example code should be a complete working example suitable for copying
+ and pasting and running by the reader (where possible, provide a link to a
+ file to download);
+ example code should be short;
+ example code should be commented very extensively, with the assumption
+ that this code may be read by a Twisted newcomer;
+ example code should conform to the coding standard ; and
+ example code should exhibit 'best practice', not only for dealing with
+ the target functionality, but also for use of the application framework
+ and so on.
+
+
+
The requirement to have a complete working example will occasionally
+ impose upon authors the need to have a few dummy functions: in Twisted
+ documentation the most common example is where a function is needed to
+ generate a Deferred and fire it after some time has passed. An example
+ might be this, where deferLater
is used to fire a callback
+ after a period of time:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .internet import task , reactor
+
+ def getDummyDeferred ():
+ """
+ Dummy method which returns a deferred that will fire in 5 seconds with
+ a result
+ """
+ return task .deferLater (reactor , 5 , lambda x : "RESULT" )
+
+
+
As in the above example, it is imperative to clearly mark that the
+ function is a dummy in as many ways as you can: using Dummy
in
+ the function name, explaining that it is a dummy in the docstring, and
+ marking particular lines as being required to create an effect for the
+ purposes of demonstration. In most cases, this will save the reader from
+ mistaking this dummy method for an idiom they should use in their Twisted
+ code.
+
+
Conclusions
+
+
The conclusion of a howto should follow the very last section heading
+ in a file. This heading would usually be called "Conclusion".
+
+
The conclusion of a howto should remind the reader of the tasks that
+ they have done while reading the document. For example:
+
+
+
+ In this document, you have seen how to:
+
+
+
+ set up an anonymous read-only SFTP server;
+ set up a SFTP server where users authenticate;
+ set up a SFTP server where users are restricted to some parts of the
+ filesystem based on authentication; and
+ set up a SFTP server where users have write access to some parts of
+ the filesystem based on authentication.
+
+
+
+
If appropriate, the howto could follow this description with links to
+ other documents that might be of interest to the reader with their
+ newfound knowledge. However, these links should be limited to fairly
+ obvious extensions of at least one of the listed tasks.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/development/security.html b/doc/core/development/security.html
new file mode 100644
index 0000000..78286d1
--- /dev/null
+++ b/doc/core/development/security.html
@@ -0,0 +1,43 @@
+
+
+Twisted Documentation: Security
+
+
+
+
+ Security
+
+
+
+
+
+
We need to do a full audit of Twisted, module by module.
+This document list the sort of things you want to look for
+when doing this, or when writing your own code.
+
+
Bad input
+
+
Any place we receive untrusted data, we need to be careful.
+In some cases we are not careful enough. For example, in HTTP
+there are many places where strings need to be converted to
+ints, so we use int()
. The problem
+is that this well accept negative numbers as well, whereas
+the protocol should only be accepting positive numbers.
+
+
Resource Exhaustion and DoS
+
+
Make sure we never allow users to create arbitarily large
+strings or files. Some of the protocols still have issues
+like this. Place a limit which allows reasonable use but
+will cut off huge requests, and allow changing of this limit.
+
+
+
Another operation to look out for are exceptions. They can fill
+up logs and take a lot of CPU time to render in web pages.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/examples/ampclient.py b/doc/core/examples/ampclient.py
new file mode 100644
index 0000000..5349448
--- /dev/null
+++ b/doc/core/examples/ampclient.py
@@ -0,0 +1,26 @@
+from twisted.internet import reactor, defer
+from twisted.internet.protocol import ClientCreator
+from twisted.protocols import amp
+from ampserver import Sum, Divide
+
+
+def doMath():
+ d1 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Sum, a=13, b=81)).addCallback(
+ lambda result: result['total'])
+ def trapZero(result):
+ result.trap(ZeroDivisionError)
+ print "Divided by zero: returning INF"
+ return 1e1000
+ d2 = ClientCreator(reactor, amp.AMP).connectTCP(
+ '127.0.0.1', 1234).addCallback(
+ lambda p: p.callRemote(Divide, numerator=1234,
+ denominator=0)).addErrback(trapZero)
+ def done(result):
+ print 'Done with math:', result
+ defer.DeferredList([d1, d2]).addCallback(done)
+
+if __name__ == '__main__':
+ doMath()
+ reactor.run()
diff --git a/doc/core/examples/ampserver.py b/doc/core/examples/ampserver.py
new file mode 100644
index 0000000..7b5adf0
--- /dev/null
+++ b/doc/core/examples/ampserver.py
@@ -0,0 +1,40 @@
+from twisted.protocols import amp
+
+class Sum(amp.Command):
+ arguments = [('a', amp.Integer()),
+ ('b', amp.Integer())]
+ response = [('total', amp.Integer())]
+
+
+class Divide(amp.Command):
+ arguments = [('numerator', amp.Integer()),
+ ('denominator', amp.Integer())]
+ response = [('result', amp.Float())]
+ errors = {ZeroDivisionError: 'ZERO_DIVISION'}
+
+
+class Math(amp.AMP):
+ def sum(self, a, b):
+ total = a + b
+ print 'Did a sum: %d + %d = %d' % (a, b, total)
+ return {'total': total}
+ Sum.responder(sum)
+
+ def divide(self, numerator, denominator):
+ result = float(numerator) / denominator
+ print 'Divided: %d / %d = %f' % (numerator, denominator, result)
+ return {'result': result}
+ Divide.responder(divide)
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.internet.protocol import Factory
+ pf = Factory()
+ pf.protocol = Math
+ reactor.listenTCP(1234, pf)
+ print 'started'
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/bananabench.py b/doc/core/examples/bananabench.py
new file mode 100644
index 0000000..a712afa
--- /dev/null
+++ b/doc/core/examples/bananabench.py
@@ -0,0 +1,79 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+import sys
+import time
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+# Twisted Imports
+from twisted.spread import banana
+from twisted.internet import protocol
+
+iterationCount = 10000
+
+class BananaBench:
+ r = range( iterationCount )
+ def setUp(self, encClass):
+ self.io = StringIO.StringIO()
+ self.enc = encClass()
+ self.enc.makeConnection(protocol.FileWrapper(self.io))
+ self.enc._selectDialect("none")
+ self.enc.expressionReceived = self.putResult
+
+ def putResult(self, result):
+ self.result = result
+
+ def tearDown(self):
+ self.enc.connectionLost()
+ del self.enc
+
+ def testEncode(self, value):
+ starttime = time.time()
+ for i in self.r:
+ self.enc.sendEncoded(value)
+ self.io.truncate(0)
+ endtime = time.time()
+ print ' Encode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def testDecode(self, value):
+ self.enc.sendEncoded(value)
+ encoded = self.io.getvalue()
+ starttime = time.time()
+ for i in self.r:
+ self.enc.dataReceived(encoded)
+ endtime = time.time()
+ print ' Decode took %s seconds' % (endtime - starttime)
+ return endtime - starttime
+
+ def performTest(self, method, data, encClass):
+ self.setUp(encClass)
+ method(data)
+ self.tearDown()
+
+ def runTests(self, testData):
+ print 'Test data is: %s' % testData
+ print ' Using Pure Python Banana:'
+ self.performTest(self.testEncode, testData, banana.Banana)
+ self.performTest(self.testDecode, testData, banana.Banana)
+
+bench = BananaBench()
+print 'Doing %s iterations of each test.' % iterationCount
+print ''
+testData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+bench.runTests(testData)
+testData = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
+bench.runTests(testData)
+testData = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+bench.runTests(testData)
+testData = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]
+bench.runTests(testData)
+testData = [1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l, 10l]
+bench.runTests(testData)
+testData = [1, 2, [3, 4], [30.5, 40.2], 5, ["six", "seven", ["eight", 9]], [10], []]
+bench.runTests(testData)
+
diff --git a/doc/core/examples/chatserver.py b/doc/core/examples/chatserver.py
new file mode 100644
index 0000000..76c3cf8
--- /dev/null
+++ b/doc/core/examples/chatserver.py
@@ -0,0 +1,37 @@
+"""The most basic chat protocol possible.
+
+run me with twistd -y chatserver.py, and then connect with multiple
+telnet clients to port 1025
+"""
+
+from twisted.protocols import basic
+
+
+
+class MyChat(basic.LineReceiver):
+ def connectionMade(self):
+ print "Got new client!"
+ self.factory.clients.append(self)
+
+ def connectionLost(self, reason):
+ print "Lost a client!"
+ self.factory.clients.remove(self)
+
+ def lineReceived(self, line):
+ print "received", repr(line)
+ for c in self.factory.clients:
+ c.message(line)
+
+ def message(self, message):
+ self.transport.write(message + '\n')
+
+
+from twisted.internet import protocol
+from twisted.application import service, internet
+
+factory = protocol.ServerFactory()
+factory.protocol = MyChat
+factory.clients = []
+
+application = service.Application("chatserver")
+internet.TCPServer(1025, factory).setServiceParent(application)
diff --git a/doc/core/examples/courier.py b/doc/core/examples/courier.py
new file mode 100644
index 0000000..db80a15
--- /dev/null
+++ b/doc/core/examples/courier.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example of a interfacing to Courier's mail filter interface.
+"""
+
+LOGFILE = '/tmp/filter.log'
+
+# Setup log file
+from twisted.python import log
+log.startLogging(open(LOGFILE, 'a'))
+import sys
+sys.stderr = log.logfile
+
+# Twisted imports
+from twisted.internet import reactor, stdio
+from twisted.internet.protocol import Protocol, Factory
+from twisted.protocols import basic
+
+FILTERS='/var/lib/courier/filters'
+ALLFILTERS='/var/lib/courier/allfilters'
+FILTERNAME='twistedfilter'
+
+import os, os.path
+from syslog import syslog, openlog, LOG_MAIL
+from rfc822 import Message
+
+def trace_dump():
+ t,v,tb = sys.exc_info()
+ openlog(FILTERNAME, 0, LOG_MAIL)
+ syslog('Unhandled exception: %s - %s' % (v, t))
+ while tb:
+ syslog('Trace: %s:%s %s' % (tb.tb_frame.f_code.co_filename,tb.tb_frame.f_code.co_name,tb.tb_lineno))
+ tb = tb.tb_next
+ # just to be safe
+ del tb
+
+def safe_del(file):
+ try:
+ if os.path.isdir(file):
+ os.removedirs(file)
+ else:
+ os.remove(file)
+ except OSError:
+ pass
+
+
+class DieWhenLost(Protocol):
+ def connectionLost(self, reason=None):
+ reactor.stop()
+
+
+class MailProcessor(basic.LineReceiver):
+ """I process a mail message.
+
+ Override filterMessage to do any filtering you want."""
+ messageFilename = None
+ delimiter = '\n'
+
+ def connectionMade(self):
+ log.msg('Connection from %r' % self.transport)
+ self.state = 'connected'
+ self.metaInfo = []
+
+ def lineReceived(self, line):
+ if self.state == 'connected':
+ self.messageFilename = line
+ self.state = 'gotMessageFilename'
+ if self.state == 'gotMessageFilename':
+ if line:
+ self.metaInfo.append(line)
+ else:
+ if not self.metaInfo:
+ self.transport.loseConnection()
+ return
+ self.filterMessage()
+
+ def filterMessage(self):
+ """Override this.
+
+ A trivial example is included.
+ """
+ try:
+ m = Message(open(self.messageFilename))
+ self.sendLine('200 Ok')
+ except:
+ trace_dump()
+ self.sendLine('435 %s processing error' % FILTERNAME)
+
+
+def main():
+ # Listen on the UNIX socket
+ f = Factory()
+ f.protocol = MailProcessor
+ safe_del('%s/%s' % (ALLFILTERS, FILTERNAME))
+ reactor.listenUNIX('%s/%s' % (ALLFILTERS, FILTERNAME), f, 10)
+
+ # Once started, close fd 3 to let Courier know we're ready
+ reactor.callLater(0, os.close, 3)
+
+ # When stdin is closed, it's time to exit.
+ s = stdio.StandardIO(DieWhenLost())
+
+ # Go!
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/cred.py b/doc/core/examples/cred.py
new file mode 100644
index 0000000..6fabc9a
--- /dev/null
+++ b/doc/core/examples/cred.py
@@ -0,0 +1,163 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+
+import sys
+from zope.interface import implements, Interface
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from twisted.python import log
+
+from twisted.cred import error
+from twisted.cred import portal
+from twisted.cred import checkers
+from twisted.cred import credentials
+
+class IProtocolUser(Interface):
+ def getPrivileges():
+ """Return a list of privileges this user has."""
+
+ def logout():
+ """Cleanup per-login resources allocated to this avatar"""
+
+class AnonymousUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3]
+
+ def logout(self):
+ print "Cleaning up anonymous user resources"
+
+class RegularUser:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return [1, 2, 3, 5, 6]
+
+ def logout(self):
+ print "Cleaning up regular user resources"
+
+class Administrator:
+ implements(IProtocolUser)
+
+ def getPrivileges(self):
+ return range(50)
+
+ def logout(self):
+ print "Cleaning up administrator resources"
+
+class Protocol(basic.LineReceiver):
+ user = None
+ portal = None
+ avatar = None
+ logout = None
+
+ def connectionMade(self):
+ self.sendLine("Login with USER followed by PASS or ANON")
+ self.sendLine("Check privileges with PRIVS")
+
+ def connectionLost(self, reason):
+ if self.logout:
+ self.logout()
+ self.avatar = None
+ self.logout = None
+
+ def lineReceived(self, line):
+ f = getattr(self, 'cmd_' + line.upper().split()[0])
+ if f:
+ try:
+ f(*line.split()[1:])
+ except TypeError:
+ self.sendLine("Wrong number of arguments.")
+ except:
+ self.sendLine("Server error (probably your fault)")
+
+ def cmd_ANON(self):
+ if self.portal:
+ self.portal.login(credentials.Anonymous(), None, IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_USER(self, name):
+ self.user = name
+ self.sendLine("Alright. Now PASS?")
+
+ def cmd_PASS(self, password):
+ if not self.user:
+ self.sendLine("USER required before PASS")
+ else:
+ if self.portal:
+ self.portal.login(
+ credentials.UsernamePassword(self.user, password),
+ None,
+ IProtocolUser
+ ).addCallbacks(self._cbLogin, self._ebLogin
+ )
+ else:
+ self.sendLine("DENIED")
+
+ def cmd_PRIVS(self):
+ self.sendLine("You have the following privileges: ")
+ self.sendLine(" ".join(map(str, self.avatar.getPrivileges())))
+
+ def _cbLogin(self, (interface, avatar, logout)):
+ assert interface is IProtocolUser
+ self.avatar = avatar
+ self.logout = logout
+ self.sendLine("Login successful. Available commands: PRIVS")
+
+ def _ebLogin(self, failure):
+ failure.trap(error.UnauthorizedLogin)
+ self.sendLine("Login denied! Go away.")
+
+class ServerFactory(protocol.ServerFactory):
+ protocol = Protocol
+
+ def __init__(self, portal):
+ self.portal = portal
+
+ def buildProtocol(self, addr):
+ p = protocol.ServerFactory.buildProtocol(self, addr)
+ p.portal = self.portal
+ return p
+
+class Realm:
+ implements(portal.IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IProtocolUser in interfaces:
+ if avatarId == checkers.ANONYMOUS:
+ av = AnonymousUser()
+ elif avatarId.isupper():
+ # Capitalized usernames are administrators.
+ av = Administrator()
+ else:
+ av = RegularUser()
+ return IProtocolUser, av, av.logout
+ raise NotImplementedError("Only IProtocolUser interface is supported by this realm")
+
+def main():
+ r = Realm()
+ p = portal.Portal(r)
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ c.addUser("auser", "thepass")
+ c.addUser("SECONDUSER", "secret")
+ p.registerChecker(c)
+ p.registerChecker(checkers.AllowAnonymousAccess())
+
+ f = ServerFactory(p)
+
+ log.startLogging(sys.stdout)
+
+ from twisted.internet import reactor
+ reactor.listenTCP(4738, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/dbcred.py b/doc/core/examples/dbcred.py
new file mode 100755
index 0000000..c1c216e
--- /dev/null
+++ b/doc/core/examples/dbcred.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Simple example of a db checker: define a L{ICredentialsChecker} implementation
+that deals with a database backend to authenticate a user.
+"""
+
+from twisted.cred import error
+from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword
+from twisted.cred.checkers import ICredentialsChecker
+from twisted.internet.defer import Deferred
+
+from zope.interface import implements
+
+
+class DBCredentialsChecker(object):
+ """
+ This class checks the credentials of incoming connections
+ against a user table in a database.
+ """
+ implements(ICredentialsChecker)
+
+ def __init__(self, runQuery,
+ query="SELECT username, password FROM user WHERE username = %s",
+ customCheckFunc=None, caseSensitivePasswords=True):
+ """
+ @param runQuery: This will be called to get the info from the db.
+ Generally you'd want to create a
+ L{twisted.enterprice.adbapi.ConnectionPool} and pass it's runQuery
+ method here. Otherwise pass a function with the same prototype.
+ @type runQuery: C{callable}
+
+ @type query: query used to authenticate user.
+ @param query: C{str}
+
+ @param customCheckFunc: Use this if the passwords in the db are stored
+ as hashes. We'll just call this, so you can do the checking
+ yourself. It takes the following params:
+ (username, suppliedPass, dbPass) and must return a boolean.
+ @type customCheckFunc: C{callable}
+
+ @param caseSensitivePasswords: If true requires that every letter in
+ C{credentials.password} is exactly the same case as the it's
+ counterpart letter in the database.
+ This is only relevant if C{customCheckFunc} is not used.
+ @type caseSensitivePasswords: C{bool}
+ """
+ self.runQuery = runQuery
+ self.caseSensitivePasswords = caseSensitivePasswords
+ self.customCheckFunc = customCheckFunc
+ # We can't support hashed password credentials if we only have a hash
+ # in the DB
+ if customCheckFunc:
+ self.credentialInterfaces = (IUsernamePassword,)
+ else:
+ self.credentialInterfaces = (
+ IUsernamePassword, IUsernameHashedPassword,)
+
+ self.sql = query
+
+ def requestAvatarId(self, credentials):
+ """
+ Authenticates the kiosk against the database.
+ """
+ # Check that the credentials instance implements at least one of our
+ # interfaces
+ for interface in self.credentialInterfaces:
+ if interface.providedBy(credentials):
+ break
+ else:
+ raise error.UnhandledCredentials()
+ # Ask the database for the username and password
+ dbDeferred = self.runQuery(self.sql, (credentials.username,))
+ # Setup our deferred result
+ deferred = Deferred()
+ dbDeferred.addCallbacks(self._cbAuthenticate, self._ebAuthenticate,
+ callbackArgs=(credentials, deferred),
+ errbackArgs=(credentials, deferred))
+ return deferred
+
+ def _cbAuthenticate(self, result, credentials, deferred):
+ """
+ Checks to see if authentication was good. Called once the info has
+ been retrieved from the DB.
+ """
+ if len(result) == 0:
+ # Username not found in db
+ deferred.errback(error.UnauthorizedLogin('Username unknown'))
+ else:
+ username, password = result[0]
+ if self.customCheckFunc:
+ # Let the owner do the checking
+ if self.customCheckFunc(
+ username, credentials.password, password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # It's up to us or the credentials object to do the checking
+ # now
+ if IUsernameHashedPassword.providedBy(credentials):
+ # Let the hashed password checker do the checking
+ if credentials.checkPassword(password):
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ elif IUsernamePassword.providedBy(credentials):
+ # Compare the passwords, deciging whether or not to use
+ # case sensitivity
+ if self.caseSensitivePasswords:
+ passOk = (
+ password.lower() == credentials.password.lower())
+ else:
+ passOk = password == credentials.password
+ # See if they match
+ if passOk:
+ deferred.callback(credentials.username)
+ else:
+ deferred.errback(
+ error.UnauthorizedLogin('Password mismatch'))
+ else:
+ # OK, we don't know how to check this
+ deferred.errback(error.UnhandledCredentials())
+
+ def _ebAuthenticate(self, message, credentials, deferred):
+ """
+ The database lookup failed for some reason.
+ """
+ deferred.errback(error.LoginFailed(message))
+
+
+def main():
+ """
+ Run a simple echo pb server to test the checker. It defines a custom query
+ for dealing with sqlite special quoting, but otherwise it's a
+ straightforward use of the object.
+
+ You can test it running C{pbechoclient.py}.
+ """
+ import sys
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ import os
+ if os.path.isfile('testcred'):
+ os.remove('testcred')
+ from twisted.enterprise import adbapi
+ pool = adbapi.ConnectionPool('pysqlite2.dbapi2', 'testcred')
+ # Create the table that will be used
+ query1 = """CREATE TABLE user (
+ username string,
+ password string
+ )"""
+ # Insert a test user
+ query2 = """INSERT INTO user VALUES ('guest', 'guest')"""
+ def cb(res):
+ pool.runQuery(query2)
+ pool.runQuery(query1).addCallback(cb)
+
+ checker = DBCredentialsChecker(pool.runQuery,
+ query="SELECT username, password FROM user WHERE username = ?")
+ from twisted.cred.portal import Portal
+
+ import pbecho
+ from twisted.spread import pb
+ portal = Portal(pbecho.SimpleRealm())
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+
+
+if __name__ == "__main__":
+ from twisted.internet import reactor
+ reactor.callWhenRunning(main)
+ reactor.run()
+
diff --git a/doc/core/examples/echoclient.py b/doc/core/examples/echoclient.py
new file mode 100644
index 0000000..6bb6750
--- /dev/null
+++ b/doc/core/examples/echoclient.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+import sys
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectTCP('localhost', 8000, factory)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoclient_ssl.py b/doc/core/examples/echoclient_ssl.py
new file mode 100755
index 0000000..d905cd5
--- /dev/null
+++ b/doc/core/examples/echoclient_ssl.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from OpenSSL import SSL
+import sys
+
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import ssl, reactor
+
+
+class EchoClient(LineReceiver):
+ end="Bye-bye!"
+ def connectionMade(self):
+ self.sendLine("Hello, world!")
+ self.sendLine("What a fine day it is.")
+ self.sendLine(self.end)
+
+ def connectionLost(self, reason):
+ print 'connection lost (protocol)'
+
+ def lineReceived(self, line):
+ print "receive:", line
+ if line==self.end:
+ self.transport.loseConnection()
+
+class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed:', reason.getErrorMessage()
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost:', reason.getErrorMessage()
+ reactor.stop()
+
+def main():
+ factory = EchoClientFactory()
+ reactor.connectSSL('localhost', 8000, factory, ssl.ClientContextFactory())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoclient_udp.py b/doc/core/examples/echoclient_udp.py
new file mode 100644
index 0000000..93589a1
--- /dev/null
+++ b/doc/core/examples/echoclient_udp.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+class EchoClientDatagramProtocol(DatagramProtocol):
+ strings = [
+ "Hello, world!",
+ "What a fine day it is.",
+ "Bye-bye!"
+ ]
+
+ def startProtocol(self):
+ self.transport.connect('127.0.0.1', 8000)
+ self.sendDatagram()
+
+ def sendDatagram(self):
+ if len(self.strings):
+ datagram = self.strings.pop(0)
+ self.transport.write(datagram)
+ else:
+ reactor.stop()
+
+ def datagramReceived(self, datagram, host):
+ print 'Datagram received: ', repr(datagram)
+ self.sendDatagram()
+
+def main():
+ protocol = EchoClientDatagramProtocol()
+ t = reactor.listenUDP(0, protocol)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoserv.py b/doc/core/examples/echoserv.py
new file mode 100644
index 0000000..023a4e3
--- /dev/null
+++ b/doc/core/examples/echoserv.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import Protocol, Factory
+from twisted.internet import reactor
+
+### Protocol Implementation
+
+# This is just about the simplest possible protocol
+class Echo(Protocol):
+ def dataReceived(self, data):
+ """
+ As soon as any data is received, write it back.
+ """
+ self.transport.write(data)
+
+
+def main():
+ f = Factory()
+ f.protocol = Echo
+ reactor.listenTCP(8000, f)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/echoserv_ssl.py b/doc/core/examples/echoserv_ssl.py
new file mode 100644
index 0000000..5037b92
--- /dev/null
+++ b/doc/core/examples/echoserv_ssl.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from OpenSSL import SSL
+
+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
+
+
+if __name__ == '__main__':
+ import echoserv, sys
+ from twisted.internet.protocol import Factory
+ from twisted.internet import ssl, reactor
+ from twisted.python import log
+ log.startLogging(sys.stdout)
+ factory = Factory()
+ factory.protocol = echoserv.Echo
+ reactor.listenSSL(8000, factory, ServerContextFactory())
+ reactor.run()
diff --git a/doc/core/examples/echoserv_udp.py b/doc/core/examples/echoserv_udp.py
new file mode 100644
index 0000000..5d9eaa4
--- /dev/null
+++ b/doc/core/examples/echoserv_udp.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+# Here's a UDP version of the simplest possible protocol
+class EchoUDP(DatagramProtocol):
+ def datagramReceived(self, datagram, address):
+ self.transport.write(datagram, address)
+
+def main():
+ reactor.listenUDP(8000, EchoUDP())
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/filewatch.py b/doc/core/examples/filewatch.py
new file mode 100644
index 0000000..19a0373
--- /dev/null
+++ b/doc/core/examples/filewatch.py
@@ -0,0 +1,17 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+#
+from twisted.application import internet
+
+def watch(fp):
+ fp.seek(fp.tell())
+ for line in fp.readlines():
+ sys.stdout.write(line)
+
+import sys
+from twisted.internet import reactor
+s = internet.TimerService(0.1, watch, file(sys.argv[1]))
+s.startService()
+reactor.run()
+s.stopService()
diff --git a/doc/core/examples/ftpclient.py b/doc/core/examples/ftpclient.py
new file mode 100644
index 0000000..c911888
--- /dev/null
+++ b/doc/core/examples/ftpclient.py
@@ -0,0 +1,113 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the FTP client
+"""
+
+# Twisted imports
+from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
+from twisted.internet.protocol import Protocol, ClientCreator
+from twisted.python import usage
+from twisted.internet import reactor
+
+# Standard library imports
+import string
+import sys
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+class BufferingProtocol(Protocol):
+ """Simple utility class that holds all data written to it in a buffer."""
+ def __init__(self):
+ self.buffer = StringIO()
+
+ def dataReceived(self, data):
+ self.buffer.write(data)
+
+# Define some callbacks
+
+def success(response):
+ print 'Success! Got response:'
+ print '---'
+ if response is None:
+ print None
+ else:
+ print string.join(response, '\n')
+ print '---'
+
+
+def fail(error):
+ print 'Failed. Error was:'
+ print error
+
+def showFiles(result, fileListProtocol):
+ print 'Processed file listing:'
+ for file in fileListProtocol.files:
+ print ' %s: %d bytes, %s' \
+ % (file['filename'], file['size'], file['date'])
+ print 'Total: %d files' % (len(fileListProtocol.files))
+
+def showBuffer(result, bufferProtocol):
+ print 'Got data:'
+ print bufferProtocol.buffer.getvalue()
+
+
+class Options(usage.Options):
+ optParameters = [['host', 'h', 'localhost'],
+ ['port', 'p', 21],
+ ['username', 'u', 'anonymous'],
+ ['password', None, 'twisted@'],
+ ['passive', None, 0],
+ ['debug', 'd', 1],
+ ]
+
+def run():
+ # Get config
+ config = Options()
+ config.parseOptions()
+ config.opts['port'] = int(config.opts['port'])
+ config.opts['passive'] = int(config.opts['passive'])
+ config.opts['debug'] = int(config.opts['debug'])
+
+ # Create the client
+ FTPClient.debug = config.opts['debug']
+ creator = ClientCreator(reactor, FTPClient, config.opts['username'],
+ config.opts['password'], passive=config.opts['passive'])
+ creator.connectTCP(config.opts['host'], config.opts['port']).addCallback(connectionMade).addErrback(connectionFailed)
+ reactor.run()
+
+def connectionFailed(f):
+ print "Connection Failed:", f
+ reactor.stop()
+
+def connectionMade(ftpClient):
+ # Get the current working directory
+ ftpClient.pwd().addCallbacks(success, fail)
+
+ # Get a detailed listing of the current directory
+ fileList = FTPFileListProtocol()
+ d = ftpClient.list('.', fileList)
+ d.addCallbacks(showFiles, fail, callbackArgs=(fileList,))
+
+ # Change to the parent directory
+ ftpClient.cdup().addCallbacks(success, fail)
+
+ # Create a buffer
+ proto = BufferingProtocol()
+
+ # Get short listing of current directory, and quit when done
+ d = ftpClient.nlst('.', proto)
+ d.addCallbacks(showBuffer, fail, callbackArgs=(proto,))
+ d.addCallback(lambda result: reactor.stop())
+
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ run()
+
diff --git a/doc/core/examples/ftpserver.py b/doc/core/examples/ftpserver.py
new file mode 100644
index 0000000..8c4588b
--- /dev/null
+++ b/doc/core/examples/ftpserver.py
@@ -0,0 +1,55 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+An example FTP server with minimal user authentication.
+"""
+
+from twisted.protocols.ftp import FTPFactory, FTPRealm
+from twisted.cred.portal import Portal
+from twisted.cred.checkers import AllowAnonymousAccess, FilePasswordDB
+from twisted.internet import reactor
+
+#
+# First, set up a portal (twisted.cred.portal.Portal). This will be used
+# to authenticate user logins, including anonymous logins.
+#
+# Part of this will be to establish the "realm" of the server - the most
+# important task in this case is to establish where anonymous users will
+# have default access to. In a real world scenario this would typically
+# point to something like '/pub' but for this example it is pointed at the
+# current working directory.
+#
+# The other important part of the portal setup is to point it to a list of
+# credential checkers. In this case, the first of these is used to grant
+# access to anonymous users and is relatively simple; the second is a very
+# primitive password checker. This example uses a plain text password file
+# that has one username:password pair per line. This checker *does* provide
+# a hashing interface, and one would normally want to use it instead of
+# plain text storage for anything remotely resembling a 'live' network. In
+# this case, the file "pass.dat" is used, and stored in the same directory
+# as the server. BAD.
+#
+# Create a pass.dat file which looks like this:
+#
+# =====================
+# jeff:bozo
+# grimmtooth:bozo2
+# =====================
+#
+p = Portal(FTPRealm('./'),
+ [AllowAnonymousAccess(), FilePasswordDB("pass.dat")])
+
+#
+# Once the portal is set up, start up the FTPFactory and pass the portal to
+# it on startup. FTPFactory will start up a twisted.protocols.ftp.FTP()
+# handler for each incoming OPEN request. Business as usual in Twisted land.
+#
+f = FTPFactory(p)
+
+#
+# You know this part. Point the reactor to port 21 coupled with the above factory,
+# and start the event loop.
+#
+reactor.listenTCP(21, f)
+reactor.run()
diff --git a/doc/core/examples/gpsfix.py b/doc/core/examples/gpsfix.py
new file mode 100644
index 0000000..eefb578
--- /dev/null
+++ b/doc/core/examples/gpsfix.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+GPSTest is a simple example using the SerialPort transport and the NMEA 0183
+and Rockwell Zodiac GPS protocols to display fix data as it is received from
+the device.
+"""
+from twisted.python import log, usage
+import sys
+
+if sys.platform == 'win32':
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+
+class GPSFixLogger:
+ def handle_fix(self, *args):
+ """
+ handle_fix gets called whenever either rockwell.Zodiac or nmea.NMEAReceiver
+ receives and decodes fix data. Generally, GPS receivers will report a
+ fix at 1hz. Implementing only this method is sufficient for most purposes
+ unless tracking of ground speed, course, utc date, or detailed satellite
+ information is necessary.
+
+ For example, plotting a map from MapQuest or a similar service only
+ requires longitude and latitude.
+ """
+ log.msg('fix:\n' +
+ '\n'.join(map(lambda n: ' %s = %s' % tuple(n), zip(('utc', 'lon', 'lat', 'fix', 'sat', 'hdp', 'alt', 'geo', 'dgp'), map(repr, args)))))
+
+class GPSOptions(usage.Options):
+ optFlags = [
+ ['zodiac', 'z', 'Use Rockwell Zodiac (DeLorme Earthmate) [default: NMEA 0183]'],
+ ]
+ optParameters = [
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ['baudrate', 'b', None, 'Serial baudrate [default: 4800 for NMEA, 9600 for Zodiac]'],
+ ['port', 'p', '/dev/ttyS0', 'Serial Port device'],
+ ]
+
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+
+ o = GPSOptions()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print '%s: %s' % (sys.argv[0], errortext)
+ print '%s: Try --help for usage details.' % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = o.opts['outfile']
+ if logFile is None:
+ logFile = sys.stdout
+ log.startLogging(logFile)
+
+ if o.opts['zodiac']:
+ from twisted.protocols.gps.rockwell import Zodiac as GPSProtocolBase
+ baudrate = 9600
+ else:
+ from twisted.protocols.gps.nmea import NMEAReceiver as GPSProtocolBase
+ baudrate = 4800
+ class GPSTest(GPSProtocolBase, GPSFixLogger):
+ pass
+
+ if o.opts['baudrate']:
+ baudrate = int(o.opts['baudrate'])
+
+
+ port = o.opts['port']
+ log.msg('Attempting to open %s at %dbps as a %s device' % (port, baudrate, GPSProtocolBase.__name__))
+ s = SerialPort(GPSTest(), o.opts['port'], reactor, baudrate=baudrate)
+ reactor.run()
diff --git a/doc/core/examples/index.html b/doc/core/examples/index.html
new file mode 100644
index 0000000..118e28d
--- /dev/null
+++ b/doc/core/examples/index.html
@@ -0,0 +1,125 @@
+
+
+Twisted Documentation: Twisted code examples
+
+
+
+
+ Twisted code examples
+
+
+
+
+
Simple Echo server and client
+
+
+
Chat
+
+
+
Echo server & client variants
+
+
+
AMP server & client variants
+
+
+
Perspective Broker
+
+
+
Cred
+
+ cred.py - Authenticate a user with an in-memory username/password
+ database
+ dbcred.py - Using a database backend to authenticate a user
+
+
+
GUI
+
+
+
FTP examples
+
+ ftpclient.py - example of using the FTP client
+ ftpserver.py - create an FTP server which
+ serves files for anonymous users from the working directory and serves
+ files for authenticated users from /home
.
+
+
+
Logging
+
+
+
POSIX Specific Tricks
+
+
+
Miscellaneous
+
+ shaper.py - example of rate-limiting your web server
+ stdiodemo.py - example using stdio, Deferreds, LineReceiver
+ and twisted.web.client.
+ mouse.py - example using MouseMan protocol with the SerialPort
+ transport
+ ptyserv.py - serve shells in pseudo-terminals over TCP
+ courier.py - example of interfacing to Courier's mail filter
+ interface
+ longex.py - example of doing arbitarily long calculations nicely
+ in Twisted
+ longex2.py - using generators to do long calculations
+ stdin.py - reading a line at a time from standard input
+ without blocking the reactor
+ streaming.py - example of a push producer/consumer system
+ filewatch.py - write the content of a file to standard out
+ one line at a time
+ shoutcast.py - example Shoutcast client
+ gpsfix.py - example using the SerialPort transport and GPS
+ protocols to display fix data as it is received from the device
+ wxacceptance.py - acceptance tests for wxreactor
+ postfix.py - test application for PostfixTCPMapServer
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/examples/longex.py b/doc/core/examples/longex.py
new file mode 100644
index 0000000..6fc9a7f
--- /dev/null
+++ b/doc/core/examples/longex.py
@@ -0,0 +1,66 @@
+"""Simple example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+"""
+
+from twisted.protocols import basic
+from twisted.internet import reactor
+from twisted.internet.protocol import ServerFactory
+
+class LongMultiplicationProtocol(basic.LineReceiver):
+ """A protocol for doing long multiplications.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The answer is calculated in chunks, so no one
+ calculation should block for long enough to matter.
+ """
+ def connectionMade(self):
+ self.workQueue = []
+
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+
+ if len(numbers) <= 1:
+ self.sendLine('Error.')
+ return
+
+ self.workQueue.append(numbers)
+ reactor.callLater(0, self.calcChunk)
+
+ def calcChunk(self):
+ # Make sure there's some work left; when multiple lines are received
+ # while processing is going on, multiple calls to reactor.callLater()
+ # can happen between calls to calcChunk().
+ if self.workQueue:
+ # Get the first bit of work off the queue
+ work = self.workQueue[0]
+
+ # Do a chunk of work: [a, b, c, ...] -> [a*b, c, ...]
+ work[:2] = [work[0] * work[1]]
+
+ # If this piece of work now has only one element, send it.
+ if len(work) == 1:
+ self.sendLine(str(work[0]))
+ del self.workQueue[0]
+
+ # Schedule this function to do more work, if there's still work
+ # to be done.
+ if self.workQueue:
+ reactor.callLater(0, self.calcChunk)
+
+
+class LongMultiplicationFactory(ServerFactory):
+ protocol = LongMultiplicationProtocol
+
+
+if __name__ == '__main__':
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(1234, LongMultiplicationFactory())
+ reactor.run()
+
diff --git a/doc/core/examples/longex2.py b/doc/core/examples/longex2.py
new file mode 100644
index 0000000..8758988
--- /dev/null
+++ b/doc/core/examples/longex2.py
@@ -0,0 +1,101 @@
+"""Example of doing arbitarily long calculations nicely in Twisted.
+
+This is also a simple demonstration of twisted.protocols.basic.LineReceiver.
+This example uses generators to do the calculation. It also tries to be
+a good example in division of responsibilities:
+- The protocol handles the wire layer, reading in lists of numbers
+ and writing out the result.
+- The factory decides on policy, and has relatively little knowledge
+ of the details of the protocol. Other protocols can use the same
+ factory class by intantiating and setting .protocol
+- The factory does little job itself: it is mostly a policy maker.
+ The 'smarts' are in free-standing functions which are written
+ for flexibility.
+
+The goal is for minimal dependencies:
+- You can use runIterator to run any iterator inside the Twisted
+ main loop.
+- You can use multiply whenever you need some way of multiplying
+ numbers such that the multiplications will happen asynchronously,
+ but it is your responsibility to schedule the multiplications.
+- You can use the protocol with other factories to implement other
+ functions that apply to arbitrary lists of longs.
+- You can use the factory with other protocols for support of legacy
+ protocols. In fact, the factory does not even have to be used as
+ a protocol factory. Here are easy ways to support the operation
+ over XML-RPC and PB.
+
+class Multiply(xmlrpc.XMLRPC):
+ def __init__(self): self.factory = Multiplication()
+ def xmlrpc_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+class Multiply(pb.Referencable):
+ def __init__(self): self.factory = Multiplication()
+ def remote_multiply(self, *numbers):
+ return self.factory.calc(map(long, numbers))
+
+Note:
+Multiplying zero numbers is a perfectly sensible operation, and the
+result is 1. In that, this example departs from doc/examples/longex.py,
+which errors out when trying to do this.
+"""
+from __future__ import generators
+from twisted.protocols import basic
+from twisted.internet import defer, protocol
+
+def runIterator(reactor, iterator):
+ try:
+ iterator.next()
+ except StopIteration:
+ pass
+ else:
+ reactor.callLater(0, runIterator, reactor, iterator)
+
+def multiply(numbers):
+ d = defer.Deferred()
+ def _():
+ acc = 1
+ while numbers:
+ acc *= numbers.pop()
+ yield None
+ d.callback(acc)
+ return d, _()
+
+class Numbers(basic.LineReceiver):
+ """Protocol for reading lists of numbers and manipulating them.
+
+ It receives a list of numbers (seperated by whitespace) on a line, and
+ writes back the answer. The exact algorithm to use depends on the
+ factory. It should return an str-able Deferred.
+ """
+ def lineReceived(self, line):
+ try:
+ numbers = map(long, line.split())
+ except ValueError:
+ self.sendLine('Error.')
+ return
+ deferred = self.factory.calc(numbers)
+ deferred.addCallback(str)
+ deferred.addCallback(self.sendLine)
+
+class Multiplication(protocol.ServerFactory):
+ """Factory for multiplying numbers.
+
+ It provides a function which calculates the multiplication
+ of a list of numbers. The function destroys its input.
+ Note that instances of this factory can use other formats
+ for transmitting the number lists, as long as they set
+ correct protoocl values.
+ """
+ protocol = Numbers
+ def calc(self, numbers):
+ deferred, iterator = multiply(numbers)
+ from twisted.internet import reactor
+ runIterator(reactor, iterator)
+ return deferred
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ reactor.listenTCP(1234, Multiplication())
+ reactor.run()
diff --git a/doc/core/examples/mouse.py b/doc/core/examples/mouse.py
new file mode 100755
index 0000000..a62ed81
--- /dev/null
+++ b/doc/core/examples/mouse.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using MouseMan protocol with the SerialPort transport.
+"""
+
+# TODO set tty modes, etc.
+# This works for me:
+
+# speed 1200 baud; rows 0; columns 0; line = 0;
+# intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D;
+# eol = ; eol2 = ; start = ^Q; stop = ^S; susp = ^Z;
+# rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
+# -parenb -parodd cs7 hupcl -cstopb cread clocal -crtscts ignbrk
+# -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon
+# -ixoff -iuclc -ixany -imaxbel -opost -olcuc -ocrnl -onlcr -onocr
+# -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten
+# -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl
+# -echoke
+
+import sys
+from twisted.python import usage, log
+from twisted.protocols.mice import mouseman
+
+if sys.platform == 'win32':
+ # win32 serial does not work yet!
+ raise NotImplementedError, "The SerialPort transport does not currently support Win32"
+ from twisted.internet import win32eventreactor
+ win32eventreactor.install()
+
+class Options(usage.Options):
+ optParameters = [
+ ['port', 'p', '/dev/mouse', 'Device for serial mouse'],
+ ['baudrate', 'b', '1200', 'Baudrate for serial mouse'],
+ ['outfile', 'o', None, 'Logfile [default: sys.stdout]'],
+ ]
+
+class McFooMouse(mouseman.MouseMan):
+ def down_left(self):
+ log.msg("LEFT")
+
+ def up_left(self):
+ log.msg("left")
+
+ def down_middle(self):
+ log.msg("MIDDLE")
+
+ def up_middle(self):
+ log.msg("middle")
+
+ def down_right(self):
+ log.msg("RIGHT")
+
+ def up_right(self):
+ log.msg("right")
+
+ def move(self, x, y):
+ log.msg("(%d,%d)" % (x, y))
+
+if __name__ == '__main__':
+ from twisted.internet import reactor
+ from twisted.internet.serialport import SerialPort
+ o = Options()
+ try:
+ o.parseOptions()
+ except usage.UsageError, errortext:
+ print "%s: %s" % (sys.argv[0], errortext)
+ print "%s: Try --help for usage details." % (sys.argv[0])
+ raise SystemExit, 1
+
+ logFile = sys.stdout
+ if o.opts['outfile']:
+ logFile = o.opts['outfile']
+ log.startLogging(logFile)
+
+ SerialPort(McFooMouse(), o.opts['port'], reactor, baudrate=int(o.opts['baudrate']))
+ reactor.run()
diff --git a/doc/core/examples/pb_exceptions.py b/doc/core/examples/pb_exceptions.py
new file mode 100644
index 0000000..00753a4
--- /dev/null
+++ b/doc/core/examples/pb_exceptions.py
@@ -0,0 +1,36 @@
+
+from twisted.python import util
+from twisted.spread import pb
+from twisted.cred import portal, checkers, credentials
+
+class Avatar(pb.Avatar):
+ def perspective_exception(self, x):
+ return x / 0
+
+class Realm:
+ def requestAvatar(self, interface, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ return pb.IPerspective, Avatar(), lambda: None
+
+def cbLogin(avatar):
+ avatar.callRemote("exception", 10).addCallback(str).addCallback(util.println)
+
+def ebLogin(failure):
+ print failure
+
+def main():
+ c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user="pass")
+ p = portal.Portal(Realm(), [c])
+ server = pb.PBServerFactory(p)
+ server.unsafeTracebacks = True
+ client = pb.PBClientFactory()
+ login = client.login(credentials.UsernamePassword("user", "pass"))
+ login.addCallback(cbLogin).addErrback(ebLogin).addBoth(lambda: reactor.stop())
+
+ from twisted.internet import reactor
+ p = reactor.listenTCP(0, server)
+ c = reactor.connectTCP('127.0.0.1', p.getHost().port, client)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbbenchclient.py b/doc/core/examples/pbbenchclient.py
new file mode 100644
index 0000000..9cd2b31
--- /dev/null
+++ b/doc/core/examples/pbbenchclient.py
@@ -0,0 +1,42 @@
+
+from twisted.spread import pb
+from twisted.internet import defer, reactor
+from twisted.cred.credentials import UsernamePassword
+import time
+
+class PBBenchClient:
+ hostname = 'localhost'
+ portno = pb.portno
+ calledThisSecond = 0
+
+ def callLoop(self, ignored):
+ d1 = self.persp.callRemote("simple")
+ d2 = self.persp.callRemote("complexTypes")
+ defer.DeferredList([d1, d2]).addCallback(self.callLoop)
+ self.calledThisSecond += 1
+ thisSecond = int(time.time())
+ if thisSecond != self.lastSecond:
+ if thisSecond - self.lastSecond > 1:
+ print "WARNING it took more than one second"
+ print 'cps:', self.calledThisSecond
+ self.calledThisSecond = 0
+ self.lastSecond = thisSecond
+
+ def _cbPerspective(self, persp):
+ self.persp = persp
+ self.lastSecond = int(time.time())
+ self.callLoop(None)
+
+ def runTest(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP(self.hostname, self.portno, factory)
+ factory.login(UsernamePassword("benchmark", "benchmark")).addCallback(self._cbPerspective)
+
+
+def main():
+ PBBenchClient().runTest()
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbbenchserver.py b/doc/core/examples/pbbenchserver.py
new file mode 100644
index 0000000..324f23f
--- /dev/null
+++ b/doc/core/examples/pbbenchserver.py
@@ -0,0 +1,54 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Server for PB benchmark."""
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred.portal import IRealm
+
+class PBBenchPerspective(pb.Avatar):
+ callsPerSec = 0
+ def __init__(self):
+ pass
+
+ def perspective_simple(self):
+ self.callsPerSec = self.callsPerSec + 1
+ return None
+
+ def printCallsPerSec(self):
+ print '(s) cps:', self.callsPerSec
+ self.callsPerSec = 0
+ reactor.callLater(1, self.printCallsPerSec)
+
+ def perspective_complexTypes(self):
+ return ['a', 1, 1l, 1.0, [], ()]
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ p = PBBenchPerspective()
+ p.printCallsPerSec()
+ return pb.IPerspective, p, lambda : None
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("benchmark", "benchmark")
+ portal.registerChecker(checker)
+ reactor.listenTCP(8787, pb.PBServerFactory(portal))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/pbecho.py b/doc/core/examples/pbecho.py
new file mode 100644
index 0000000..45df949
--- /dev/null
+++ b/doc/core/examples/pbecho.py
@@ -0,0 +1,51 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+if __name__ == '__main__':
+ # Avoid using any names defined in the "__main__" module.
+ from pbecho import main
+ raise SystemExit(main())
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred.portal import IRealm
+
+class DefinedError(pb.Error):
+ pass
+
+
+class SimplePerspective(pb.Avatar):
+
+ def perspective_echo(self, text):
+ print 'echoing',text
+ return text
+
+ def perspective_error(self):
+ raise DefinedError("exception!")
+
+ def logout(self):
+ print self, "logged out"
+
+
+class SimpleRealm:
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective in interfaces:
+ avatar = SimplePerspective()
+ return pb.IPerspective, avatar, avatar.logout
+ else:
+ raise NotImplementedError("no interface")
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.cred.portal import Portal
+ from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+ portal = Portal(SimpleRealm())
+ checker = InMemoryUsernamePasswordDatabaseDontUse()
+ checker.addUser("guest", "guest")
+ portal.registerChecker(checker)
+ reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
+ reactor.run()
diff --git a/doc/core/examples/pbechoclient.py b/doc/core/examples/pbechoclient.py
new file mode 100644
index 0000000..b7435f7
--- /dev/null
+++ b/doc/core/examples/pbechoclient.py
@@ -0,0 +1,32 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+
+from pbecho import DefinedError
+
+def success(message):
+ print "Message received:",message
+ # reactor.stop()
+
+def failure(error):
+ t = error.trap(DefinedError)
+ print "error received:", t
+ reactor.stop()
+
+def connected(perspective):
+ perspective.callRemote('echo', "hello world").addCallbacks(success, failure)
+ perspective.callRemote('error').addCallbacks(success, failure)
+ print "connected."
+
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", pb.portno, factory)
+factory.login(
+ UsernamePassword("guest", "guest")).addCallbacks(connected, failure)
+
+reactor.run()
diff --git a/doc/core/examples/pbgtk2.py b/doc/core/examples/pbgtk2.py
new file mode 100644
index 0000000..b5f3e7f
--- /dev/null
+++ b/doc/core/examples/pbgtk2.py
@@ -0,0 +1,122 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from __future__ import nested_scopes
+
+from twisted.internet import gtk2reactor
+gtk2reactor.install()
+
+import gtk
+from gtk import glade
+from twisted import copyright
+from twisted.internet import reactor, defer
+from twisted.python import failure, log, util
+from twisted.spread import pb
+from twisted.cred.credentials import UsernamePassword
+from twisted.internet import error as netError
+
+
+class LoginDialog:
+ def __init__(self, deferred):
+ self.deferredResult = deferred
+
+ gladefile = util.sibpath(__file__, "pbgtk2login.glade")
+ self.glade = glade.XML(gladefile)
+
+ self.glade.signal_autoconnect(self)
+
+ self.setWidgetsFromGladefile()
+ self._loginDialog.show()
+
+ def setWidgetsFromGladefile(self):
+ widgets = ("hostEntry", "portEntry", "userNameEntry", "passwordEntry",
+ "statusBar", "loginDialog")
+ gw = self.glade.get_widget
+ for widgetName in widgets:
+ setattr(self, "_" + widgetName, gw(widgetName))
+
+ self._statusContext = self._statusBar.get_context_id("Login dialog.")
+
+ def on_loginDialog_response(self, widget, response):
+ handlers = {gtk.RESPONSE_NONE: self.windowClosed,
+ gtk.RESPONSE_DELETE_EVENT: self.windowClosed,
+ gtk.RESPONSE_OK: self.doLogin,
+ gtk.RESPONSE_CANCEL: self.cancelled}
+ handlers.get(response)()
+
+ def on_loginDialog_close(self, widget, userdata=None):
+ self.windowClosed()
+
+ def cancelled(self):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+ self._loginDialog.destroy()
+
+ def windowClosed(self, reason=None):
+ if not self.deferredResult.called:
+ self.deferredResult.errback()
+
+ def doLogin(self):
+ host = self._hostEntry.get_text()
+ port = int(self._portEntry.get_text())
+ userName = self._userNameEntry.get_text()
+ password = self._passwordEntry.get_text()
+
+ client_factory = pb.PBClientFactory()
+ reactor.connectTCP(host, port, client_factory)
+ creds = UsernamePassword(userName, password)
+ client_factory.login(creds).addCallbacks(self._cbGotPerspective, self._ebFailedLogin)
+
+ self.statusMsg("Contacting server...")
+
+ def _cbGotPerspective(self, perspective):
+ self.statusMsg("Connected to server.")
+ self.deferredResult.callback(perspective)
+ self._loginDialog.destroy()
+
+ def _ebFailedLogin(self, reason):
+ if isinstance(reason, failure.Failure):
+ text = str(reason.value)
+ else:
+ text = str(reason)
+
+ self.statusMsg(text)
+ msg = gtk.MessageDialog(self._loginDialog,
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_CLOSE,
+ text)
+ msg.show_all()
+ msg.connect("response", lambda *a: msg.destroy())
+
+ def statusMsg(self, text):
+ self._statusBar.push(self._statusContext, text)
+
+
+class EchoClient:
+ def __init__(self, echoer):
+ self.echoer = echoer
+ w = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ vb = gtk.VBox(); b = gtk.Button("Echo:")
+ self.entry = gtk.Entry(); self.outry = gtk.Entry()
+ w.add(vb)
+ map(vb.add, [b, self.entry, self.outry])
+ b.connect('clicked', self.clicked)
+ w.connect('destroy', self.stop)
+ w.show_all()
+
+ def clicked(self, b):
+ txt = self.entry.get_text()
+ self.entry.set_text("")
+ self.echoer.callRemote('echo',txt).addCallback(self.outry.set_text)
+
+ def stop(self, b):
+ reactor.stop()
+
+d = defer.Deferred()
+LoginDialog(d)
+d.addCallbacks(EchoClient,
+ lambda _: reactor.stop())
+
+reactor.run()
diff --git a/doc/core/examples/pbgtk2login.glade b/doc/core/examples/pbgtk2login.glade
new file mode 100644
index 0000000..6b5eb01
--- /dev/null
+++ b/doc/core/examples/pbgtk2login.glade
@@ -0,0 +1,330 @@
+
+
+
+
+
+
+ Login
+ GTK_WINDOW_TOPLEVEL
+ GTK_WIN_POS_NONE
+ False
+ True
+ True
+ True
+
+
+
+
+
+ True
+ False
+ 0
+
+
+
+ True
+ GTK_BUTTONBOX_END
+
+
+
+ True
+ True
+ True
+ gtk-cancel
+ True
+ GTK_RELIEF_NORMAL
+ -6
+
+
+
+
+
+ True
+ True
+ True
+ True
+ GTK_RELIEF_NORMAL
+ -5
+
+
+
+ True
+ 0.5
+ 0.5
+ 0
+ 0
+
+
+
+ True
+ False
+ 2
+
+
+
+ True
+ gtk-ok
+ 4
+ 0.5
+ 0.5
+ 0
+ 0
+
+
+ 0
+ False
+ False
+
+
+
+
+
+ True
+ _Login
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0.5
+ 0.5
+ 0
+ 0
+
+
+ 0
+ False
+ False
+
+
+
+
+
+
+
+
+
+
+ 0
+ False
+ True
+ GTK_PACK_END
+
+
+
+
+
+ True
+ False
+
+
+ 0
+ False
+ False
+ GTK_PACK_END
+
+
+
+
+
+ True
+ 3
+ 2
+ False
+ 0
+ 0
+
+
+
+ True
+ _Host:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0.9
+ 0.5
+ 0
+ 0
+ hostEntry
+
+
+
+
+
+
+ 0
+ 1
+ 0
+ 1
+ fill
+
+
+
+
+
+
+ True
+ False
+ 0
+
+
+
+ True
+ The name of a host to connect to.
+ True
+ True
+ True
+ True
+ 0
+ localhost
+ True
+ *
+ True
+
+
+
+
+
+ 0
+ True
+ True
+
+
+
+
+
+ True
+ The number of a port to connect on.
+ True
+ True
+ True
+ 0
+ 8787
+ True
+ *
+ True
+ 5
+
+
+ 0
+ False
+ True
+
+
+
+
+ 1
+ 2
+ 0
+ 1
+ fill
+
+
+
+
+
+ True
+ _Name:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0.9
+ 0.5
+ 0
+ 0
+ userNameEntry
+
+
+ 0
+ 1
+ 1
+ 2
+ fill
+
+
+
+
+
+
+ True
+ An identity to log in as.
+ True
+ True
+ True
+ 0
+ guest
+ True
+ *
+ True
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
+
+
+
+ True
+ The Identity's log-in password.
+ True
+ True
+ False
+ 0
+ guest
+ True
+ *
+ True
+
+
+ 1
+ 2
+ 2
+ 3
+
+
+
+
+
+
+ True
+ _Password:
+ True
+ False
+ GTK_JUSTIFY_LEFT
+ False
+ False
+ 0.9
+ 0.5
+ 0
+ 0
+ passwordEntry
+
+
+ 0
+ 1
+ 2
+ 3
+ fill
+
+
+
+
+
+ 0
+ False
+ False
+
+
+
+
+
+
+
diff --git a/doc/core/examples/pbinterop.py b/doc/core/examples/pbinterop.py
new file mode 100644
index 0000000..167121e
--- /dev/null
+++ b/doc/core/examples/pbinterop.py
@@ -0,0 +1,71 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""PB interop server."""
+
+from twisted.spread import pb, jelly, flavors
+from twisted.internet import reactor
+
+
+class Interop(pb.Root):
+ """Test object for PB interop tests."""
+
+ def __init__(self):
+ self.o = pb.Referenceable()
+
+ def remote_int(self):
+ return 1
+
+ def remote_string(self):
+ return "string"
+
+ def remote_unicode(self):
+ return u"string"
+
+ def remote_float(self):
+ return 1.5
+
+ def remote_list(self):
+ return [1, 2, 3]
+
+ def remote_recursive(self):
+ l = []
+ l.append(l)
+ return l
+
+ def remote_dict(self):
+ return {1 : 2}
+
+ def remote_reference(self):
+ return self.o
+
+ def remote_local(self, obj):
+ d = obj.callRemote("hello")
+ d.addCallback(self._local_success)
+
+ def _local_success(self, result):
+ if result != "hello, world":
+ raise ValueError, "%r != %r" % (result, "hello, world")
+
+ def remote_receive(self, obj):
+ expected = [1, 1.5, "hi", u"hi", {1 : 2}]
+ if obj != expected:
+ raise ValueError, "%r != %r" % (obj, expected)
+
+ def remote_self(self, obj):
+ if obj != self:
+ raise ValueError, "%r != %r" % (obj, self)
+
+ def remote_copy(self, x):
+ o = flavors.Copyable()
+ o.x = x
+ return o
+
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Interop()))
+ reactor.run()
+
+
+
diff --git a/doc/core/examples/pbsimple.py b/doc/core/examples/pbsimple.py
new file mode 100644
index 0000000..68e4cbf
--- /dev/null
+++ b/doc/core/examples/pbsimple.py
@@ -0,0 +1,16 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Echoer(pb.Root):
+ def remote_echo(self, st):
+ print 'echoing:', st
+ return st
+
+if __name__ == '__main__':
+ reactor.listenTCP(8789, pb.PBServerFactory(Echoer()))
+ reactor.run()
diff --git a/doc/core/examples/pbsimpleclient.py b/doc/core/examples/pbsimpleclient.py
new file mode 100644
index 0000000..05cb50a
--- /dev/null
+++ b/doc/core/examples/pbsimpleclient.py
@@ -0,0 +1,18 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.python import util
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8789, factory)
+d = factory.getRootObject()
+d.addCallback(lambda object: object.callRemote("echo", "hello network"))
+d.addCallback(lambda echo: 'server echoed: '+echo)
+d.addErrback(lambda reason: 'error: '+str(reason.value))
+d.addCallback(util.println)
+d.addCallback(lambda _: reactor.stop())
+reactor.run()
diff --git a/doc/core/examples/postfix.py b/doc/core/examples/postfix.py
new file mode 100644
index 0000000..c13d640
--- /dev/null
+++ b/doc/core/examples/postfix.py
@@ -0,0 +1,29 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Test app for PostfixTCPMapServer.
+
+Call with parameters KEY1=VAL1 KEY2=VAL2 ...
+"""
+
+import sys
+
+from twisted.internet import reactor
+from twisted.protocols import postfix
+from twisted.python import log
+
+log.startLogging(sys.stdout)
+
+d = {}
+for arg in sys.argv[1:]:
+ try:
+ k,v = arg.split('=', 1)
+ except ValueError:
+ k = arg
+ v = ''
+ d[k] = v
+
+f = postfix.PostfixTCPMapDictServerFactory(d)
+port = reactor.listenTCP(4242, f, interface='127.0.0.1')
+reactor.run()
diff --git a/doc/core/examples/ptyserv.py b/doc/core/examples/ptyserv.py
new file mode 100644
index 0000000..4e736a9
--- /dev/null
+++ b/doc/core/examples/ptyserv.py
@@ -0,0 +1,45 @@
+# Copyright (c) Twisted Matrix Laboratories
+# See LICENSE for details
+
+"""
+A PTY server that spawns a shell upon connection.
+
+Run this example by typing in:
+> python ptyserv.py
+
+Telnet to the server once you start it by typing in:
+> telnet localhost 5823
+"""
+
+from twisted.internet import reactor, protocol
+
+class FakeTelnet(protocol.Protocol):
+ commandToRun = ['/bin/sh'] # could have args too
+ dirToRunIn = '/tmp'
+ def connectionMade(self):
+ print 'connection made'
+ self.propro = ProcessProtocol(self)
+ reactor.spawnProcess(self.propro, self.commandToRun[0], self.commandToRun, {},
+ self.dirToRunIn, usePTY=1)
+ def dataReceived(self, data):
+ self.propro.transport.write(data)
+ def conectionLost(self):
+ print 'connection lost'
+ self.propro.tranport.loseConnection()
+
+class ProcessProtocol(protocol.ProcessProtocol):
+
+ def __init__(self, pr):
+ self.pr = pr
+
+ def outReceived(self, data):
+ self.pr.transport.write(data)
+
+ def processEnded(self, reason):
+ print 'protocol conection lost'
+ self.pr.transport.loseConnection()
+
+f = protocol.Factory()
+f.protocol = FakeTelnet
+reactor.listenTCP(5823, f)
+reactor.run()
diff --git a/doc/core/examples/pyui_bg.png b/doc/core/examples/pyui_bg.png
new file mode 100644
index 0000000..08d45ec
Binary files /dev/null and b/doc/core/examples/pyui_bg.png differ
diff --git a/doc/core/examples/pyuidemo.py b/doc/core/examples/pyuidemo.py
new file mode 100755
index 0000000..3b7a835
--- /dev/null
+++ b/doc/core/examples/pyuidemo.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Displays a frame with two buttons and a background image, using pyui library.
+
+Run this example by typing in:
+ python pyuidemo.py
+
+Select "Quit" button to exit demo.
+"""
+
+import pyui
+from twisted.internet import reactor, pyuisupport
+
+def onButton(self):
+ print "got a button"
+
+def onQuit(self):
+ reactor.stop()
+
+def main():
+ pyuisupport.install(args=(640, 480), kw={'renderer': '2d'})
+
+ w = pyui.widgets.Frame(50, 50, 400, 400, "clipme")
+ b = pyui.widgets.Button("A button is here", onButton)
+ q = pyui.widgets.Button("Quit!", onQuit)
+
+ w.addChild(b)
+ w.addChild(q)
+ w.pack()
+
+ w.setBackImage("pyui_bg.png")
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/recvfd.py b/doc/core/examples/recvfd.py
new file mode 100644
index 0000000..6d17c5f
--- /dev/null
+++ b/doc/core/examples/recvfd.py
@@ -0,0 +1,90 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Client-side of an example for sending file descriptors between processes over
+UNIX sockets. This client connects to a server listening on a UNIX socket and
+waits for one file descriptor to arrive over the connection. It displays the
+name of the file and the first 80 bytes it contains, then exits.
+
+To runb this example, run this program with one argument: a path giving the UNIX
+socket the server side of this example is already listening on. For example:
+
+ $ python recvfd.py /tmp/sendfd.sock
+
+See sendfd.py for the server side of this example.
+"""
+
+if __name__ == '__main__':
+ import recvfd
+ raise SystemExit(recvfd.main())
+
+import os, sys
+
+from zope.interface import implements
+
+from twisted.python.log import startLogging
+from twisted.python.filepath import FilePath
+from twisted.internet.defer import Deferred
+from twisted.internet.interfaces import IFileDescriptorReceiver
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineOnlyReceiver
+from twisted.internet.endpoints import UNIXClientEndpoint
+from twisted.internet import reactor
+
+class ReceiveFDProtocol(LineOnlyReceiver):
+ implements(IFileDescriptorReceiver)
+
+ descriptor = None
+
+ def __init__(self):
+ self.whenDisconnected = Deferred()
+
+
+ def fileDescriptorReceived(self, descriptor):
+ # Record the descriptor sent to us
+ self.descriptor = descriptor
+
+
+ def lineReceived(self, line):
+ if self.descriptor is None:
+ print "Received %r without receiving descriptor!" % (line,)
+ else:
+ # Use the previously received descriptor, along with the newly
+ # provided information about which file it is, to present some
+ # information to the user.
+ data = os.read(self.descriptor, 80)
+ print "Received %r from the server." % (line,)
+ print "First 80 bytes are:\n%r\n" % (data,)
+ os.close(self.descriptor)
+ self.transport.loseConnection()
+
+
+ def connectionLost(self, reason):
+ self.whenDisconnected.callback(None)
+
+
+
+def main():
+ address = FilePath(sys.argv[1])
+
+ startLogging(sys.stdout)
+
+ factory = Factory()
+ factory.protocol = ReceiveFDProtocol
+ factory.quiet = True
+
+ endpoint = UNIXClientEndpoint(reactor, address.path)
+ connected = endpoint.connect(factory)
+
+ def succeeded(client):
+ return client.whenDisconnected
+ def failed(reason):
+ print "Could not connect:", reason.getErrorMessage()
+ def disconnected(ignored):
+ reactor.stop()
+
+ connected.addCallbacks(succeeded, failed)
+ connected.addCallback(disconnected)
+
+ reactor.run()
diff --git a/doc/core/examples/rotatinglog.py b/doc/core/examples/rotatinglog.py
new file mode 100644
index 0000000..288753c
--- /dev/null
+++ b/doc/core/examples/rotatinglog.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using the rotating log.
+"""
+
+from twisted.python import log
+from twisted.python import logfile
+
+# rotate every 100 bytes
+f = logfile.LogFile("test.log", "/tmp", rotateLength=100)
+
+# setup logging to use our new logfile
+log.startLogging(f)
+
+# print a few message
+for i in range(10):
+ log.msg("this is a test of the logfile: %s" % i)
+
+# rotate the logfile manually
+f.rotate()
+
+log.msg("goodbye")
diff --git a/doc/core/examples/sendfd.py b/doc/core/examples/sendfd.py
new file mode 100644
index 0000000..b1948aa
--- /dev/null
+++ b/doc/core/examples/sendfd.py
@@ -0,0 +1,83 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Server-side of an example for sending file descriptors between processes over
+UNIX sockets. This server accepts connections on a UNIX socket and sends one
+file descriptor to them, along with the name of the file it is associated with.
+
+To run this example, run this program with two arguments: a path giving a UNIX
+socket to listen on (must not exist) and a path to a file to send to clients
+which connect (must exist). For example:
+
+ $ python sendfd.py /tmp/sendfd.sock /etc/motd
+
+It will listen for client connections until stopped (eg, using Control-C). Most
+interesting behavior happens on the client side.
+
+See recvfd.py for the client side of this example.
+"""
+
+if __name__ == '__main__':
+ import sendfd
+ raise SystemExit(sendfd.main())
+
+import sys
+
+from twisted.python.log import startLogging
+from twisted.python.filepath import FilePath
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineOnlyReceiver
+from twisted.internet import reactor
+
+class SendFDProtocol(LineOnlyReceiver):
+ def connectionMade(self):
+ # Open the desired file and keep a reference to it - keeping it open
+ # until we know the other side has it. Closing it early will prevent
+ # it from actually being sent.
+ self.fObj = self.factory.content.open()
+
+ # Tell the transport to send it. It is not necessarily sent when this
+ # method returns. The reactor may need to run for a while longer before
+ # that happens.
+ self.transport.sendFileDescriptor(self.fObj.fileno())
+
+ # Send along *at least* one byte, since one file descriptor was sent.
+ # In this case, send along the name of the file to let the other side
+ # have some idea what they're getting.
+ self.sendLine(self.factory.content.path)
+
+ # Give the other side a minute to deal with this. If they don't close
+ # the connection by then, we will do it for them.
+ self.timeoutCall = reactor.callLater(60, self.transport.loseConnection)
+
+
+ def connectionLost(self, reason):
+ # Clean up the file object, it is no longer needed.
+ self.fObj.close()
+ self.fObj = None
+
+ # Clean up the timeout, if necessary.
+ if self.timeoutCall.active():
+ self.timeoutCall.cancel()
+ self.timeoutCall = None
+
+
+def main():
+ address = FilePath(sys.argv[1])
+ content = FilePath(sys.argv[2])
+
+ if address.exists():
+ raise SystemExit("Cannot listen on an existing path")
+
+ if not content.isfile():
+ raise SystemExit("Content file must exist")
+
+ startLogging(sys.stdout)
+
+ serverFactory = Factory()
+ serverFactory.content = content
+ serverFactory.protocol = SendFDProtocol
+
+ port = reactor.listenUNIX(address.path, serverFactory)
+ reactor.run()
diff --git a/doc/core/examples/server.pem b/doc/core/examples/server.pem
new file mode 100644
index 0000000..80ef9dc
--- /dev/null
+++ b/doc/core/examples/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/doc/core/examples/shaper.py b/doc/core/examples/shaper.py
new file mode 100644
index 0000000..573d67c
--- /dev/null
+++ b/doc/core/examples/shaper.py
@@ -0,0 +1,52 @@
+# -*- Python -*-
+
+"""Example of rate-limiting your web server.
+
+Caveat emptor: While the transfer rates imposed by this mechanism will
+look accurate with wget's rate-meter, don't forget to examine your network
+interface's traffic statistics as well. The current implementation tends
+to create lots of small packets in some conditions, and each packet carries
+with it some bytes of overhead. Check to make sure this overhead is not
+costing you more bandwidth than you are saving by limiting the rate!
+"""
+
+from twisted.protocols import htb
+# for picklability
+import shaper
+
+serverFilter = htb.HierarchicalBucketFilter()
+serverBucket = htb.Bucket()
+
+# Cap total server traffic at 20 kB/s
+serverBucket.maxburst = 20000
+serverBucket.rate = 20000
+
+serverFilter.buckets[None] = serverBucket
+
+# Web service is also limited per-host:
+class WebClientBucket(htb.Bucket):
+ # Your first 10k is free
+ maxburst = 10000
+ # One kB/s thereafter.
+ rate = 1000
+
+webFilter = htb.FilterByHost(serverFilter)
+webFilter.bucketFactory = shaper.WebClientBucket
+
+servertype = "web" # "chargen"
+
+if servertype == "web":
+ from twisted.web import server, static
+ site = server.Site(static.File("/var/www"))
+ site.protocol = htb.ShapedProtocolFactory(site.protocol, webFilter)
+elif servertype == "chargen":
+ from twisted.protocols import wire
+ from twisted.internet import protocol
+
+ site = protocol.ServerFactory()
+ site.protocol = htb.ShapedProtocolFactory(wire.Chargen, webFilter)
+ #site.protocol = wire.Chargen
+
+from twisted.internet import reactor
+reactor.listenTCP(8000, site)
+reactor.run()
diff --git a/doc/core/examples/shoutcast.py b/doc/core/examples/shoutcast.py
new file mode 100644
index 0000000..26c7a7d
--- /dev/null
+++ b/doc/core/examples/shoutcast.py
@@ -0,0 +1,26 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example Shoutcast client. Run with:
+
+python shoutcast.py localhost 8080
+"""
+
+import sys
+
+from twisted.internet import protocol, reactor
+from twisted.protocols.shoutcast import ShoutcastClient
+
+class Test(ShoutcastClient):
+ def gotMetaData(self, data):
+ print "meta:", data
+
+ def gotMP3Data(self, data):
+ pass
+
+host = sys.argv[1]
+port = int(sys.argv[2])
+
+protocol.ClientCreator(reactor, Test).connectTCP(host, port)
+reactor.run()
diff --git a/doc/core/examples/simple.tac b/doc/core/examples/simple.tac
new file mode 100644
index 0000000..02b3f81
--- /dev/null
+++ b/doc/core/examples/simple.tac
@@ -0,0 +1,39 @@
+# You can run this .tac file directly with:
+# twistd -ny simple.tac
+
+from twisted.application import service, internet
+from twisted.protocols import wire
+from twisted.internet import protocol
+from twisted.python import util
+
+application = service.Application('test')
+s = service.IServiceCollection(application)
+factory = protocol.ServerFactory()
+factory.protocol = wire.Echo
+internet.TCPServer(8080, factory).setServiceParent(s)
+
+internet.TCPServer(8081, factory).setServiceParent(s)
+internet.TimerService(5, util.println, "--MARK--").setServiceParent(s)
+
+class Foo(protocol.Protocol):
+ def connectionMade(self):
+ self.transport.write('lalala\n')
+ def dataReceived(self, data):
+ print `data`
+
+factory = protocol.ClientFactory()
+factory.protocol = Foo
+internet.TCPClient('localhost', 8081, factory).setServiceParent(s)
+
+class FooService(service.Service):
+ def startService(self):
+ service.Service.startService(self)
+ print 'lala, starting'
+ def stopService(self):
+ service.Service.stopService(self)
+ print 'lala, stopping'
+ print self.parent.getServiceNamed(self.name) is self
+
+foo = FooService()
+foo.setName('foo')
+foo.setServiceParent(s)
diff --git a/doc/core/examples/simpleclient.py b/doc/core/examples/simpleclient.py
new file mode 100644
index 0000000..bba9f64
--- /dev/null
+++ b/doc/core/examples/simpleclient.py
@@ -0,0 +1,49 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example client. Run simpleserv.py first before running this.
+"""
+
+from twisted.internet import reactor, protocol
+
+
+# a client protocol
+
+class EchoClient(protocol.Protocol):
+ """Once connected, send a message, then print the result."""
+
+ def connectionMade(self):
+ self.transport.write("hello, world!")
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ print "Server said:", data
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ print "connection lost"
+
+class EchoFactory(protocol.ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print "Connection failed - goodbye!"
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print "Connection lost - goodbye!"
+ reactor.stop()
+
+
+# this connects the protocol to a server runing on port 8000
+def main():
+ f = EchoFactory()
+ reactor.connectTCP("localhost", 8000, f)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/simpleserv.py b/doc/core/examples/simpleserv.py
new file mode 100644
index 0000000..228fe44
--- /dev/null
+++ b/doc/core/examples/simpleserv.py
@@ -0,0 +1,26 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import reactor, protocol
+
+
+class Echo(protocol.Protocol):
+ """This is just about the simplest possible protocol"""
+
+ def dataReceived(self, data):
+ "As soon as any data is received, write it back."
+ self.transport.write(data)
+
+
+def main():
+ """This runs the protocol on port 8000"""
+ factory = protocol.ServerFactory()
+ factory.protocol = Echo
+ reactor.listenTCP(8000,factory)
+ reactor.run()
+
+# this only runs if the module was *not* imported
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/stdin.py b/doc/core/examples/stdin.py
new file mode 100644
index 0000000..e987be5
--- /dev/null
+++ b/doc/core/examples/stdin.py
@@ -0,0 +1,30 @@
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of reading a line at a time from standard input
+without blocking the reactor.
+"""
+
+from twisted.internet import stdio
+from twisted.protocols import basic
+
+class Echo(basic.LineReceiver):
+ from os import linesep as delimiter
+
+ def connectionMade(self):
+ self.transport.write('>>> ')
+
+ def lineReceived(self, line):
+ self.sendLine('Echo: ' + line)
+ self.transport.write('>>> ')
+
+def main():
+ stdio.StandardIO(Echo())
+ from twisted.internet import reactor
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/stdiodemo.py b/doc/core/examples/stdiodemo.py
new file mode 100644
index 0000000..9132428
--- /dev/null
+++ b/doc/core/examples/stdiodemo.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Example using stdio, Deferreds, LineReceiver and twisted.web.client.
+
+Note that the WebCheckerCommandProtocol protocol could easily be used in e.g.
+a telnet server instead; see the comments for details.
+
+Based on an example by Abe Fettig.
+"""
+
+from twisted.internet import stdio, reactor
+from twisted.protocols import basic
+from twisted.web import client
+
+class WebCheckerCommandProtocol(basic.LineReceiver):
+ delimiter = '\n' # unix terminal style newlines. remove this line
+ # for use with Telnet
+
+ def connectionMade(self):
+ self.sendLine("Web checker console. Type 'help' for help.")
+
+ def lineReceived(self, line):
+ # Ignore blank lines
+ if not line: return
+
+ # Parse the command
+ commandParts = line.split()
+ command = commandParts[0].lower()
+ args = commandParts[1:]
+
+ # Dispatch the command to the appropriate method. Note that all you
+ # need to do to implement a new command is add another do_* method.
+ try:
+ method = getattr(self, 'do_' + command)
+ except AttributeError, e:
+ self.sendLine('Error: no such command.')
+ else:
+ try:
+ method(*args)
+ except Exception, e:
+ self.sendLine('Error: ' + str(e))
+
+ def do_help(self, command=None):
+ """help [command]: List commands, or show help on the given command"""
+ if command:
+ self.sendLine(getattr(self, 'do_' + command).__doc__)
+ else:
+ commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')]
+ self.sendLine("Valid commands: " +" ".join(commands))
+
+ def do_quit(self):
+ """quit: Quit this session"""
+ self.sendLine('Goodbye.')
+ self.transport.loseConnection()
+
+ def do_check(self, url):
+ """check : Attempt to download the given web page"""
+ client.getPage(url).addCallback(
+ self.__checkSuccess).addErrback(
+ self.__checkFailure)
+
+ def __checkSuccess(self, pageData):
+ self.sendLine("Success: got %i bytes." % len(pageData))
+
+ def __checkFailure(self, failure):
+ self.sendLine("Failure: " + failure.getErrorMessage())
+
+ def connectionLost(self, reason):
+ # stop the reactor, only because this is meant to be run in Stdio.
+ reactor.stop()
+
+if __name__ == "__main__":
+ stdio.StandardIO(WebCheckerCommandProtocol())
+ reactor.run()
diff --git a/doc/core/examples/streaming.py b/doc/core/examples/streaming.py
new file mode 100644
index 0000000..06fcfd7
--- /dev/null
+++ b/doc/core/examples/streaming.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+This is a sample implementation of a Twisted push producer/consumer system. It
+consists of a TCP server which asks the user how many random integers they
+want, and it sends the result set back to the user, one result per line,
+and finally closes the connection.
+"""
+
+from sys import stdout
+from random import randrange
+
+from zope.interface import implements
+from twisted.python.log import startLogging
+from twisted.internet import interfaces, reactor
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineReceiver
+
+
+class Producer(object):
+ """
+ Send back the requested number of random integers to the client.
+ """
+
+ implements(interfaces.IPushProducer)
+
+ def __init__(self, proto, count):
+ self._proto = proto
+ self._goal = count
+ self._produced = 0
+ self._paused = False
+
+ def pauseProducing(self):
+ """
+ When we've produced data too fast, pauseProducing() will be called
+ (reentrantly from within resumeProducing's sendLine() method, most
+ likely), so set a flag that causes production to pause temporarily.
+ """
+ self._paused = True
+ print 'Pausing connection from %s' % self._proto.transport.getPeer()
+
+ def resumeProducing(self):
+ """
+ Resume producing integers.
+
+ This tells the push producer to (re-)add itself to the main loop and
+ produce integers for its consumer until the requested number of integers
+ were returned to the client.
+ """
+ self._paused = False
+
+ while not self._paused and self._produced < self._goal:
+ next_int = randrange(0, 10000)
+ self._proto.sendLine('%d' % next_int)
+ self._produced += 1
+
+ if self._produced == self._goal:
+ self._proto.transport.unregisterProducer()
+ self._proto.transport.loseConnection()
+
+ def stopProducing(self):
+ """
+ When a consumer has died, stop producing data for good.
+ """
+ self._produced = self._goal
+
+
+class ServeRandom(LineReceiver):
+ """
+ Serve up random integers.
+ """
+
+ def connectionMade(self):
+ """
+ Once the connection is made we ask the client how many random integers
+ the producer should return.
+ """
+ print 'Connection made from %s' % self.transport.getPeer()
+ self.sendLine('How many random integers do you want?')
+
+ def lineReceived(self, line):
+ """
+ This checks how many random integers the client expects in return and
+ tells the producer to start generating the data.
+ """
+ count = int(line.strip())
+ print 'Client requested %d random integers!' % count
+ producer = Producer(self, count)
+ self.transport.registerProducer(producer, True)
+ producer.resumeProducing()
+
+ def connectionLost(self, reason):
+ print 'Connection lost from %s' % self.transport.getPeer()
+
+
+startLogging(stdout)
+factory = Factory()
+factory.protocol = ServeRandom
+reactor.listenTCP(1234, factory)
+reactor.run()
diff --git a/doc/core/examples/testlogging.py b/doc/core/examples/testlogging.py
new file mode 100644
index 0000000..d44def7
--- /dev/null
+++ b/doc/core/examples/testlogging.py
@@ -0,0 +1,41 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""Test logging.
+
+Message should only be printed second time around.
+"""
+
+from twisted.python import log
+from twisted.internet import reactor
+
+import sys, warnings
+
+def test(i):
+ print "printed", i
+ log.msg("message %s" % i)
+ warnings.warn("warning %s" % i)
+ try:
+ raise RuntimeError, "error %s" % i
+ except:
+ log.err()
+
+def startlog():
+ log.startLogging(sys.stdout)
+
+def end():
+ reactor.stop()
+
+# pre-reactor run
+test(1)
+
+# after reactor run
+reactor.callLater(0.1, test, 2)
+reactor.callLater(0.2, startlog)
+
+# after startLogging
+reactor.callLater(0.3, test, 3)
+reactor.callLater(0.4, end)
+
+reactor.run()
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000..71cb459
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,13 @@
+{
+ IBClasses = (
+ {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; },
+ {
+ ACTIONS = {doTwistzillaFetch = id; };
+ CLASS = MyAppDelegate;
+ LANGUAGE = ObjC;
+ OUTLETS = {messageTextField = id; progressIndicator = id; resultTextField = id; };
+ SUPERCLASS = NSObject;
+ }
+ );
+ IBVersion = 1;
+}
\ No newline at end of file
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000..4f99a2d
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,24 @@
+
+
+
+
+ IBEditorPositions
+
+ 29
+ 127 344 318 44 0 0 1600 1002
+
+ IBFramework Version
+ 291.0
+ IBLockedObjects
+
+ 204
+
+ IBOpenObjects
+
+ 21
+ 29
+
+ IBSystem Version
+ 6L60
+
+
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000..e5caaf0
Binary files /dev/null and b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/English.lproj/MainMenu.nib/keyedobjects.nib differ
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
new file mode 100644
index 0000000..96010e2
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/README.txt
@@ -0,0 +1,6 @@
+Requires PyObjC 1.3.1 (svn r1589 or later)
+
+To run the demo:
+
+python setup.py py2app
+open dist/Twistzilla.app
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
new file mode 100644
index 0000000..45eeb8d
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
@@ -0,0 +1,79 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+# import needed classes/functions from Cocoa
+from Foundation import *
+from AppKit import *
+
+# import Nib loading functionality from AppKit
+from PyObjCTools import NibClassBuilder, AppHelper
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet import reactor, protocol
+from twisted.web import http
+from twisted.python import log
+import sys, urlparse
+
+# create ObjC classes as defined in MainMenu.nib
+NibClassBuilder.extractClasses("MainMenu")
+class TwistzillaClient(http.HTTPClient):
+ def __init__(self, delegate, urls):
+ self.urls = urls
+ self.delegate = delegate
+
+ def connectionMade(self):
+ self.sendCommand('GET', str(self.urls[2]))
+ self.sendHeader('Host', '%s:%d' % (self.urls[0], self.urls[1]))
+ self.sendHeader('User-Agent', 'CocoaTwistzilla')
+ self.endHeaders()
+
+ def handleResponse(self, data):
+ self.delegate.gotResponse_(data)
+
+class MyAppDelegate(NibClassBuilder.AutoBaseClass):
+ def gotResponse_(self, html):
+ s = self.resultTextField.textStorage()
+ s.replaceCharactersInRange_withString_((0, s.length()), html)
+ self.progressIndicator.stopAnimation_(self)
+
+ def doTwistzillaFetch_(self, sender):
+ s = self.resultTextField.textStorage()
+ s.deleteCharactersInRange_((0, s.length()))
+ self.progressIndicator.startAnimation_(self)
+ u = urlparse.urlparse(self.messageTextField.stringValue())
+ pos = u[1].find(':')
+ if pos == -1:
+ host, port = u[1], 80
+ else:
+ host, port = u[1][:pos], int(u[1][pos+1:])
+ if u[2] == '':
+ fname = '/'
+ else:
+ fname = u[2]
+ host = host.encode('utf8')
+ fname = fname.encode('utf8')
+ protocol.ClientCreator(reactor, TwistzillaClient, self, (host, port, fname)).connectTCP(host, port).addErrback(lambda f:self.gotResponse_(f.getBriefTraceback()))
+
+ def applicationDidFinishLaunching_(self, aNotification):
+ """
+ Invoked by NSApplication once the app is done launching and
+ immediately before the first pass through the main event
+ loop.
+ """
+ self.messageTextField.setStringValue_("http://www.twistedmatrix.com/")
+ reactor.interleave(AppHelper.callAfter)
+
+ def applicationShouldTerminate_(self, sender):
+ if reactor.running:
+ reactor.addSystemEventTrigger(
+ 'after', 'shutdown', AppHelper.stopEventLoop)
+ reactor.stop()
+ return False
+ return True
+
+if __name__ == '__main__':
+ log.startLogging(sys.stdout)
+ AppHelper.runEventLoop()
diff --git a/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
new file mode 100644
index 0000000..f3afe8a
--- /dev/null
+++ b/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
@@ -0,0 +1,14 @@
+"""
+Script for building the example.
+
+Usage:
+ python setup.py py2app
+"""
+
+from distutils.core import setup
+import py2app
+
+setup(
+ app = ['Twistzilla.py'],
+ data_files = ["English.lproj"],
+)
diff --git a/doc/core/examples/threadedselect/README b/doc/core/examples/threadedselect/README
new file mode 100644
index 0000000..5d3feab
--- /dev/null
+++ b/doc/core/examples/threadedselect/README
@@ -0,0 +1,15 @@
+The examples in this directory import a private module from the
+twisted.internet package. The _threadedselect module provides an object
+which is similar to a Twisted reactor in many ways, but which is not
+actually intended to be used in the same way as a Twisted reactor (it has a
+method named interleave which is intended to be the main entrypoint). This
+functionality should be considered highly experimental and the API subject
+to change at any time.
+
+Possibly the best way to make use of this functionality is to use it to
+implement an object which actually presents the Twisted reactor interface
+(specifically, an object with a run method). That object can then be used
+by application-code in the usual way.
+
+Another course of action is to avoid _threadedselect entirely until the
+issues surrounding it have been resolved.
diff --git a/doc/core/examples/threadedselect/blockingdemo.py b/doc/core/examples/threadedselect/blockingdemo.py
new file mode 100644
index 0000000..7dc98df
--- /dev/null
+++ b/doc/core/examples/threadedselect/blockingdemo.py
@@ -0,0 +1,92 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+from twisted.internet import _threadedselect
+_threadedselect.install()
+
+from twisted.internet.defer import Deferred
+from twisted.python.failure import Failure
+from twisted.internet import reactor
+from twisted.python.runtime import seconds
+from itertools import count
+from Queue import Queue, Empty
+
+class TwistedManager(object):
+ def __init__(self):
+ self.twistedQueue = Queue()
+ self.key = count()
+ self.results = {}
+
+ def getKey(self):
+ # get a unique identifier
+ return self.key.next()
+
+ def start(self):
+ # start the reactor
+ reactor.interleave(self.twistedQueue.put)
+
+ def _stopIterating(self, value, key):
+ self.results[key] = value
+
+ def stop(self):
+ # stop the reactor
+ key = self.getKey()
+ reactor.addSystemEventTrigger('after', 'shutdown',
+ self._stopIterating, True, key)
+ reactor.stop()
+ self.iterate(key)
+
+ def getDeferred(self, d):
+ # get the result of a deferred or raise if it failed
+ key = self.getKey()
+ d.addBoth(self._stopIterating, key)
+ res = self.iterate(key)
+ if isinstance(res, Failure):
+ res.raiseException()
+ return res
+
+ def poll(self, noLongerThan=1.0):
+ # poll the reactor for up to noLongerThan seconds
+ base = seconds()
+ try:
+ while (seconds() - base) <= noLongerThan:
+ callback = self.twistedQueue.get_nowait()
+ callback()
+ except Empty:
+ pass
+
+ def iterate(self, key=None):
+ # iterate the reactor until it has the result we're looking for
+ while key not in self.results:
+ callback = self.twistedQueue.get()
+ callback()
+ return self.results.pop(key)
+
+def fakeDeferred(msg):
+ d = Deferred()
+ def cb():
+ print "deferred called back"
+ d.callback(msg)
+ reactor.callLater(2, cb)
+ return d
+
+def fakeCallback():
+ print "twisted is still running"
+
+def main():
+ m = TwistedManager()
+ print "starting"
+ m.start()
+ print "setting up a 1sec callback"
+ reactor.callLater(1, fakeCallback)
+ print "getting a deferred"
+ res = m.getDeferred(fakeDeferred("got it!"))
+ print "got the deferred:", res
+ print "stopping"
+ m.stop()
+ print "stopped"
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/threadedselect/pygamedemo.py b/doc/core/examples/threadedselect/pygamedemo.py
new file mode 100644
index 0000000..a2bec33
--- /dev/null
+++ b/doc/core/examples/threadedselect/pygamedemo.py
@@ -0,0 +1,78 @@
+from __future__ import generators
+
+# import Twisted and install
+from twisted.internet import _threadedselect
+_threadedselect.install()
+from twisted.internet import reactor
+
+import os
+
+import pygame
+from pygame.locals import *
+
+try:
+ import pygame.fastevent as eventmodule
+except ImportError:
+ import pygame.event as eventmodule
+
+
+# You can customize this if you use your
+# own events, but you must OBEY:
+#
+# USEREVENT <= TWISTEDEVENT < NUMEVENTS
+#
+TWISTEDEVENT = USEREVENT
+
+def postTwistedEvent(func):
+ # if not using pygame.fastevent, this can explode if the queue
+ # fills up.. so that's bad. Use pygame.fastevent, in pygame CVS
+ # as of 2005-04-18.
+ eventmodule.post(eventmodule.Event(TWISTEDEVENT, iterateTwisted=func))
+
+def helloWorld():
+ print "hello, world"
+ reactor.callLater(1, helloWorld)
+reactor.callLater(1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+reactor.callLater(2, twoSecondsPassed)
+
+def eventIterator():
+ while True:
+ yield eventmodule.wait()
+ while True:
+ event = eventmodule.poll()
+ if event.type == NOEVENT:
+ break
+ else:
+ yield event
+
+def main():
+ pygame.init()
+ if hasattr(eventmodule, 'init'):
+ eventmodule.init()
+ screen = pygame.display.set_mode((300, 300))
+
+ # send an event when twisted wants attention
+ reactor.interleave(postTwistedEvent)
+ # make shouldQuit a True value when it's safe to quit
+ # by appending a value to it. This ensures that
+ # Twisted gets to shut down properly.
+ shouldQuit = []
+ reactor.addSystemEventTrigger('after', 'shutdown', shouldQuit.append, True)
+
+ for event in eventIterator():
+ if event.type == TWISTEDEVENT:
+ event.iterateTwisted()
+ if shouldQuit:
+ break
+ elif event.type == QUIT:
+ reactor.stop()
+ elif event.type == KEYDOWN and event.key == K_ESCAPE:
+ reactor.stop()
+
+ pygame.quit()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/examples/tkinterdemo.py b/doc/core/examples/tkinterdemo.py
new file mode 100644
index 0000000..1fad50e
--- /dev/null
+++ b/doc/core/examples/tkinterdemo.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+An example of using Twisted with Tkinter.
+Displays a frame with buttons that responds to mouse clicks.
+
+Run this example by typing in:
+ python tkinterdemo.py
+"""
+
+
+from Tkinter import Tk, Frame, Button, LEFT
+from twisted.internet import reactor, tksupport
+
+
+class App(object):
+
+ def onQuit(self):
+ print "Quit!"
+ reactor.stop()
+
+ def onButton(self):
+ print "Hello!"
+
+ def __init__(self, master):
+ frame = Frame(master)
+ frame.pack()
+
+ q = Button(frame, text="Quit!", command=self.onQuit)
+ b = Button(frame, text="Hello!", command=self.onButton)
+
+ q.pack(side=LEFT)
+ b.pack(side=LEFT)
+
+
+if __name__ == '__main__':
+ root = Tk()
+ tksupport.install(root)
+ app = App(root)
+ reactor.run()
diff --git a/doc/core/examples/twistd-logging.tac b/doc/core/examples/twistd-logging.tac
new file mode 100644
index 0000000..2302558
--- /dev/null
+++ b/doc/core/examples/twistd-logging.tac
@@ -0,0 +1,33 @@
+# Invoke this script with:
+
+# $ twistd -ny twistd-logging.tac
+
+# It will create a log file named "twistd-logging.log". The log file will
+# be formatted such that each line contains the representation of the dict
+# structure of each log message.
+
+from twisted.application.service import Application
+from twisted.python.log import ILogObserver, msg
+from twisted.python.util import untilConcludes
+from twisted.internet.task import LoopingCall
+
+
+logfile = open("twistd-logging.log", "a")
+
+
+def log(eventDict):
+ # untilConcludes is necessary to retry the operation when the system call
+ # has been interrupted.
+ untilConcludes(logfile.write, "Got a log! %r\n" % eventDict)
+ untilConcludes(logfile.flush)
+
+
+def logSomething():
+ msg("A log message")
+
+
+LoopingCall(logSomething).start(1)
+
+application = Application("twistd-logging")
+application.setComponent(ILogObserver, log)
+
diff --git a/doc/core/examples/wxacceptance.py b/doc/core/examples/wxacceptance.py
new file mode 100644
index 0000000..75394eb
--- /dev/null
+++ b/doc/core/examples/wxacceptance.py
@@ -0,0 +1,113 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Acceptance tests for wxreactor.
+
+Please test on Linux, Win32 and OS X:
+1. Startup event is called at startup.
+2. Scheduled event is called after 2 seconds.
+3. Shutdown takes 3 seconds, both when quiting from menu and when closing
+ window (e.g. Alt-F4 in metacity). This tests reactor.stop() and
+ wxApp.ExitEventLoop().
+4. 'hello, world' continues to be printed even when modal dialog is open
+ (use dialog menu item), when menus are held down, when window is being
+ dragged.
+"""
+
+import sys, time
+
+try:
+ from wx import Frame as wxFrame, DefaultPosition as wxDefaultPosition, \
+ Size as wxSize, Menu as wxMenu, MenuBar as wxMenuBar, \
+ EVT_MENU, MessageDialog as wxMessageDialog, App as wxApp
+except ImportError, e:
+ from wxPython.wx import *
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+from twisted.internet import reactor, defer
+
+
+# set up so that "hello, world" is printed continously
+dc = None
+def helloWorld():
+ global dc
+ print "hello, world", time.time()
+ dc = reactor.callLater(0.1, helloWorld)
+dc = reactor.callLater(0.1, helloWorld)
+
+def twoSecondsPassed():
+ print "two seconds passed"
+
+def printer(s):
+ print s
+
+def shutdown():
+ print "shutting down in 3 seconds"
+ if dc.active():
+ dc.cancel()
+ reactor.callLater(1, printer, "2...")
+ reactor.callLater(2, printer, "1...")
+ reactor.callLater(3, printer, "0...")
+ d = defer.Deferred()
+ reactor.callLater(3, d.callback, 1)
+ return d
+
+def startup():
+ print "Start up event!"
+
+reactor.callLater(2, twoSecondsPassed)
+reactor.addSystemEventTrigger("after", "startup", startup)
+reactor.addSystemEventTrigger("before", "shutdown", shutdown)
+
+
+ID_EXIT = 101
+ID_DIALOG = 102
+
+class MyFrame(wxFrame):
+ def __init__(self, parent, ID, title):
+ wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(300, 200))
+ menu = wxMenu()
+ menu.Append(ID_DIALOG, "D&ialog", "Show dialog")
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = wxMenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+ EVT_MENU(self, ID_DIALOG, self.DoDialog)
+ # you really ought to do this instead of reactor.stop() in
+ # DoExit, but for the sake of testing we'll let closing the
+ # window shutdown wx without reactor.stop(), to make sure that
+ # still does the right thing.
+ #EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoDialog(self, event):
+ dl = wxMessageDialog(self, "Check terminal to see if messages are still being "
+ "printed by Twisted.")
+ dl.ShowModal()
+ dl.Destroy()
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(wxApp):
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/doc/core/examples/wxdemo.py b/doc/core/examples/wxdemo.py
new file mode 100644
index 0000000..a9afdd3
--- /dev/null
+++ b/doc/core/examples/wxdemo.py
@@ -0,0 +1,64 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""Demo of wxPython integration with Twisted."""
+
+import sys
+
+from wx import Frame, DefaultPosition, Size, Menu, MenuBar, App
+from wx import EVT_MENU, EVT_CLOSE
+
+from twisted.python import log
+from twisted.internet import wxreactor
+wxreactor.install()
+
+# import t.i.reactor only after installing wxreactor:
+from twisted.internet import reactor
+
+
+ID_EXIT = 101
+
+class MyFrame(Frame):
+ def __init__(self, parent, ID, title):
+ Frame.__init__(self, parent, ID, title, DefaultPosition, Size(300, 200))
+ menu = Menu()
+ menu.Append(ID_EXIT, "E&xit", "Terminate the program")
+ menuBar = MenuBar()
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+ EVT_MENU(self, ID_EXIT, self.DoExit)
+
+ # make sure reactor.stop() is used to stop event loop:
+ EVT_CLOSE(self, lambda evt: reactor.stop())
+
+ def DoExit(self, event):
+ reactor.stop()
+
+
+class MyApp(App):
+
+ def twoSecondsPassed(self):
+ print "two seconds passed"
+
+ def OnInit(self):
+ frame = MyFrame(None, -1, "Hello, world")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ # look, we can use twisted calls!
+ reactor.callLater(2, self.twoSecondsPassed)
+ return True
+
+
+def demo():
+ log.startLogging(sys.stdout)
+
+ # register the App instance with Twisted:
+ app = MyApp(0)
+ reactor.registerWxApp(app)
+
+ # start the event loop:
+ reactor.run()
+
+
+if __name__ == '__main__':
+ demo()
diff --git a/doc/core/howto/amp.html b/doc/core/howto/amp.html
new file mode 100644
index 0000000..bf6a76c
--- /dev/null
+++ b/doc/core/howto/amp.html
@@ -0,0 +1,349 @@
+
+
+Twisted Documentation: A synchronous M essaging P rotocol Overview
+
+
+
+
+ A synchronous M essaging P rotocol Overview
+
+
+
+
+
The purpose of this guide is to describe the uses for and usage of twisted.protocols.amp
beyond what is explained in the API documentation. It will show you how to implement an AMP server which can respond to commands or interact directly with individual messages. It will also show you how to implement an AMP client which can issue commands to a server.
+
+
AMP is a bidirectional command/response-oriented protocol intended to be extended with application-specific request types and handlers. Various simple data types are supported and support for new data types can be added by applications.
+
+
Setting Up
+
+
AMP runs over a stream-oriented connection-based protocol, such as TCP or SSL. Before you can use any features of the AMP protocol, you need a connection. The protocol class to use to establish an AMP connection is AMP
. Connection setup works as it does for almost all protocols in Twisted. For example, you can set up a listening AMP server using a server endpoint:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+
from twisted .protocols .amp import AMP
+from twisted .internet import reactor
+from twisted .internet .protocol import Factory
+from twisted .internet .endpoints import TCP4ServerEndpoint
+from twisted .application .service import Application
+from twisted .application .internet import StreamServerEndpointService
+
+application = Application ("basic AMP server" )
+
+endpoint = TCP4ServerEndpoint (reactor , 8750 )
+factory = Factory ()
+factory .protocol = AMP
+service = StreamServerEndpointService (endpoint , factory )
+service .setServiceParent (application )
+
+
+
And you can connect to an AMP server using a client endpoint:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
if __name__ == '__main__' :
+ import basic_client
+ raise SystemExit (basic_client .main ())
+
+from sys import stdout
+
+from twisted .python .log import startLogging , err
+from twisted .protocols .amp import AMP
+from twisted .internet import reactor
+from twisted .internet .protocol import Factory
+from twisted .internet .endpoints import TCP4ClientEndpoint
+
+def connect ():
+ endpoint = TCP4ClientEndpoint (reactor , "127.0.0.1" , 8750 )
+ factory = Factory ()
+ factory .protocol = AMP
+ return endpoint .connect (factory )
+
+
+def main ():
+ startLogging (stdout )
+
+ d = connect ()
+ d .addErrback (err , "Connection failed" )
+ def done (ignored ):
+ reactor .stop ()
+ d .addCallback (done )
+
+ reactor .run ()
+
+
+
Commands
+
+
Either side of an AMP connection can issue a command to the other side. Each kind of command is represented as a subclass of Command
. A Command
defines arguments, response values, and error conditions.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
from twisted .protocols .amp import Integer , String , Unicode , Command
+
+class UsernameUnavailable (Exception ):
+ pass
+
+class RegisterUser (Command ):
+ arguments = [('username' , Unicode ()),
+ ('publickey' , String ())]
+
+ response = [('uid' , Integer ())]
+
+ errors = {UsernameUnavailable : 'username-unavailable' }
+
+
+
The definition of the command's signature - its arguments, response, and possible error conditions - is separate from the implementation of the behavior to execute when the command is received. The Command
subclass only defines the former.
+
+
Commands are issued by calling callRemote
on either side of the connection. This method returns a Deferred
which eventually fires with the result of the command.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
if __name__ == '__main__' :
+ import command_client
+ raise SystemExit (command_client .main ())
+
+from sys import stdout
+
+from twisted .python .log import startLogging , err
+from twisted .protocols .amp import Integer , String , Unicode , Command
+from twisted .internet import reactor
+
+from basic_client import connect
+
+class UsernameUnavailable (Exception ):
+ pass
+
+
+class RegisterUser (Command ):
+ arguments = [('username' , Unicode ()),
+ ('publickey' , String ())]
+
+ response = [('uid' , Integer ())]
+
+ errors = {UsernameUnavailable : 'username-unavailable' }
+
+
+def main ():
+ startLogging (stdout )
+
+ d = connect ()
+ def connected (protocol ):
+ return protocol .callRemote (
+ RegisterUser ,
+ username =u'alice' ,
+ publickey ='ssh-rsa AAAAB3NzaC1yc2 alice@actinium' )
+ d .addCallback (connected )
+
+ def registered (result ):
+ print 'Registration result:' , result
+ d .addCallback (registered )
+
+ d .addErrback (err , "Failed to register" )
+
+ def finished (ignored ):
+ reactor .stop ()
+ d .addCallback (finished )
+
+ reactor .run ()
+
+
+
Locators
+
+
+
The logic for handling a command can be specified as an object separate from the AMP
instance which interprets and formats bytes over the network.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+
from twisted .protocols .amp import CommandLocator
+from twisted .python .filepath import FilePath
+
+class UsernameUnavailable (Exception ):
+ pass
+
+class UserRegistration (CommandLocator ):
+ uidCounter = 0
+
+ @RegisterUser .responder
+ def register (self , username , publickey ):
+ path = FilePath (username )
+ if path .exists ():
+ raise UsernameUnavailable ()
+ self .uidCounter += 1
+ path .setContent ('%d %s\n' % (self .uidCounter , publickey ))
+ return self .uidCounter
+
+
+
When you define a separate CommandLocator
subclass, use it by passing an instance of it to the AMP
initializer.
+
+
1
+2
+
factory = Factory ()
+factory .protocol = lambda : AMP (locator =UserRegistration ())
+
+
+
If no locator is passed in, AMP
acts as its own locator. Command responders can be defined on an AMP
subclass, just as the responder was defined on the UserRegistration
example above.
+
+
Box Receivers
+
+
AMP conversations consist of an exchange of messages called boxes . A box consists of a sequence of pairs of key and value (for example, the pair username
and alice
). Boxes are generally represented as dict
instances. Normally boxes are passed back and forth to implement the command request/response features described above. The logic for handling each box can be specified as an object separate from the AMP
instance.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+
from zope .interface import implements
+
+from twisted .protocols .amp import IBoxReceiver
+
+class BoxReflector (object ):
+ implements (IBoxReceiver )
+
+ def startReceivingBoxes (self , boxSender ):
+ self .boxSender = boxSender
+
+ def ampBoxReceived (self , box ):
+ self .boxSender .sendBox (box )
+
+ def stopReceivingBoxes (self , reason ):
+ self .boxSender = None
+
+
+
These methods parallel those of IProtocol
. Startup notification is given by startReceivingBoxes
. The argument passed to it is an IBoxSender
provider, which can be used to send boxes back out over the network. ampBoxReceived
delivers notification for a complete box having been received. And last, stopReceivingBoxes
notifies the object that no more boxes will be received and no more can be sent. The argument passed to it is a Failure
which may contain details about what caused the conversation to end.
+
+
To use a custom IBoxReceiver
, pass it to the AMP
initializer.
+
+
1
+2
+
factory = Factory ()
+factory .protocol = lambda : AMP (boxReceiver =BoxReflector ())
+
+
+
If no box receiver is passed in, AMP
acts as its own box receiver. It handles boxes by treating them as command requests or responses and delivering them to the appropriate responder or as a result to a callRemote
Deferred
.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/application.html b/doc/core/howto/application.html
new file mode 100644
index 0000000..7b8f186
--- /dev/null
+++ b/doc/core/howto/application.html
@@ -0,0 +1,398 @@
+
+
+Twisted Documentation: Using the Twisted Application Framework
+
+
+
+
+ Using the Twisted Application Framework
+
+
+
+
+
+
Introduction
+
+
Audience
+
+
The target audience of this document is a Twisted user who wants to deploy a
+significant amount of Twisted code in a re-usable, standard and easily
+configurable fashion. A Twisted user who wishes to use the Application
+framework needs to be familiar with developing Twisted servers and/or clients .
+
+
Goals
+
+
+ To introduce the Twisted Application infrastructure.
+
+ To explain how to deploy your Twisted application using .tac
+ files and twistd
+
+ To outline the existing Twisted services.
+
+
+
Overview
+
+
The Twisted Application infrastructure takes care of running and stopping
+your application. Using this infrastructure frees you from from having to
+write a large amount of boilerplate code by hooking your application into
+existing tools that manage daemonization, logging, choosing a reactor and more.
+
+
The major tool that manages Twisted applications is a command-line utility
+called twistd
. twistd
is cross platform, and is the
+recommended tool for running Twisted applications.
+
+
+
The core component of the Twisted Application infrastructure is the twisted.application.service.Application
object â an
+object which represents your application. However, Application doesn't provide
+anything that you'd want to manipulate directly. Instead, Application acts as
+a container of any Services (objects implementing IService
) that your application
+provides. Most of your interaction with the Application infrastructure will be
+done through Services.
+
+
By Service , we mean anything in your application that can be started
+and stopped. Typical services include web servers, FTP servers and SSH
+clients. Your Application object can contain many services, and can even
+contain structured hierarchies of Services using IServiceCollection
s.
+
+
Here's a simple example of constructing an Application object which
+represents an echo server that runs on TCP port 7001.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .application import internet , service
+from somemodule import EchoFactory
+
+port = 7001
+factory = EchoFactory ()
+
+
+application = service .Application ("echo" )
+echoService = internet .TCPServer (port , factory )
+
+echoService .setServiceParent (application )
+
+
+
See Writing Servers for an explanation of
+EchoFactory.
+
+
This example creates a simple hierarchy:
+
+ application
+ |
+ `- echoService
+ More complicated hierarchies of services can be created using
+IServiceCollection. You will most likely want to do this to manage Services
+which are dependent on other Services. For example, a proxying Twisted
+application might want its server Service to only start up after the associated
+Client service.
+
+
+
Using application
+
+
twistd and tac
+
+
To handle start-up and configuration of your Twisted application, the
+Twisted Application infrastructure uses .tac
files.
+.tac
are Python files which configure an Application
object and assign this
+object to the top-level variable application
.
+
+
The following is a simple example of a .tac
file:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
+
+
+"""
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+"""
+
+import os
+from twisted .application import service , internet
+from twisted .web import static , server
+
+def getWebService ():
+ """
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ """
+
+ fileServer = server .Site (static .File (os .getcwd ()))
+ return internet .TCPServer (8080 , fileServer )
+
+
+
+application = service .Application ("Demo application" )
+
+
+service = getWebService ()
+service .setServiceParent (application )
+
+
+
twistd
is a program that runs Twisted applications using a
+.tac
file. In its most simple form, it takes a single argument
+-y
and a tac file name. For example, you can run the above server
+with the command twistd -y service.tac
.
+
+
By default, twistd
daemonizes and logs to a file called
+twistd.log
. More usually, when debugging, you will want your
+application to run in the foreground and log to the command line. To run the
+above file like this, use the command twistd -noy
+service.tac
+
+
For more information, see the twistd
man page.
+
+
Customizing twistd
logging
+
+
+twistd
logging can be customized using the command
+line. This requires that a log observer factory be
+importable. Given a file named my.py
with the code:
+
+
+
1
+2
+3
+4
+
from twisted .python .log import FileLogObserver
+
+def logger ():
+ return FileLogObserver (open ("/tmp/my.log" , "w" )).emit
+
+
+
+invoking twistd --logger my.logger ...
will log
+to a file named /tmp/my.log
(this simple example could easily be
+replaced with use of the --logfile
parameter to twistd).
+
+
+
+Alternatively, the logging behavior can be customized through an API
+accessible from .tac
files. The ILogObserver
component can be
+set on an Application in order to customize the default log observer that
+twistd
will use.
+
+
+
+Here is an example of how to use DailyLogFile
, which rotates the log once
+per day.
+
+
+
1
+2
+3
+4
+5
+6
+7
+
from twisted .application .service import Application
+from twisted .python .log import ILogObserver , FileLogObserver
+from twisted .python .logfile import DailyLogFile
+
+application = Application ("myapp" )
+logfile = DailyLogFile ("my.log" , "/tmp" )
+application .setComponent (ILogObserver , FileLogObserver (logfile ).emit )
+
+
+
+invoking twistd -y my.tac
will create a log file
+at /tmp/my.log
.
+
+
+
Services provided by Twisted
+
+
Twisted provides several services that you want to know about.
+
+
Each of these services (except TimerService) has a corresponding
+connect or listen method on the reactor, and the constructors for
+the services take the same arguments as the reactor methods. The
+connect methods are for clients and the listen methods are for
+servers. For example, TCPServer corresponds to reactor.listenTCP and TCPClient
+corresponds to reactor.connectTCP.
+
+
+ TCPServer
+
+
+ TCPClient
+
+
+
+ Services which allow you to make connections and listen for connections
+ on TCP ports.
+
+
+
+ UNIXServer
+
+ UNIXClient
+
+
+ Services which listen and make connections over UNIX sockets.
+
+
+
+ SSLServer
+
+ SSLClient
+
+ Services which allow you to make SSL connections and run SSL servers.
+
+
+
+ UDPServer
+
+ UDPClient
+
+ Services which allow you to send and receive data over UDP
+
+
+ See also the UDP documentation .
+
+
+ UNIXDatagramServer
+
+ UNIXDatagramClient
+
+ Services which send and receive data over UNIX datagram sockets.
+
+
+
+ MulticastServer
+
+
+ A server for UDP socket methods that support multicast.
+
+
+
+ TimerService
+
+
+ A service to periodically call a function.
+
+
+
+
+
Service Collection
+
+
IServiceCollection
objects contain
+IService
objects.
+IService objects can be added to IServiceCollection by calling setServiceParent
and detached
+by using disownServiceParent
.
+
+
The standard implementation of IServiceCollection is MultiService
, which also implements
+IService. MultiService is useful for creating a new Service which combines two
+or more existing Services. For example, you could create a DNS Service as a
+MultiService which has a TCP and a UDP Service as children.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
from twisted .application import internet , service
+from twisted .names import server , dns , hosts
+
+port = 53
+
+
+
+dnsService = service .MultiService ()
+hostsResolver = hosts .Resolver ('/etc/hosts' )
+tcpFactory = server .DNSServerFactory ([hostsResolver ])
+internet .TCPServer (port , tcpFactory ).setServiceParent (dnsService )
+udpFactory = dns .DNSDatagramProtocol (tcpFactory )
+internet .UDPServer (port , udpFactory ).setServiceParent (dnsService )
+
+
+application = service .Application ("DNSExample" )
+
+
+dnsService .setServiceParent (application )
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/basics.html b/doc/core/howto/basics.html
new file mode 100644
index 0000000..86970d7
--- /dev/null
+++ b/doc/core/howto/basics.html
@@ -0,0 +1,100 @@
+
+
+Twisted Documentation: The Basics
+
+
+
+
+ The Basics
+
+
+
+
+
Application
+
+
Twisted programs usually work
+with twisted.application.service.Application
.
+This class usually holds all persistent configuration of a running
+server -- ports to bind to, places where connections to must be kept
+or attempted, periodic actions to do and almost everything else. It is
+the root object in a tree of services implementing IService
.
+
+
Other HOWTOs describe how to write custom code for Applications,
+but this one describes how to use already written code (which can be
+part of Twisted or from a third-party Twisted plugin developer). The
+Twisted distribution comes with an important tool to deal with
+Applications, twistd
.
+
+
Application
s are just Python objects, which can
+be created and manipulated in the same ways as any other object.
+
+
+
twistd
+
+
The Twisted Daemon is a program that knows how to run Applications.
+This program
+is twistd(1)
. Strictly
+speaking, twistd
is not necessary --
+fetching the application, getting the IService
component,
+calling startService
, scheduling stopService
when
+the reactor shuts down, and then calling reactor.run()
could be
+done manually. twistd(1)
, however, supplies
+many options which are highly useful for program set up.
+
+
twistd
supports choosing a reactor (for more on
+reactors, see Choosing a Reactor ), logging
+to a logfile, daemonizing and more. twistd
supports all
+Applications mentioned above -- and an additional one. Sometimes
+it is convenient to write the code for building a class in straight
+Python. One big source of such Python files is the doc/examples
+directory. When a straight Python file which defines an Application
+object called application
is used, use the -y
+option.
+
+
When twistd
runs, it records its process
+id in a twistd.pid
file (this can be configured via a
+command line switch). In order to shutdown
+the twistd
process, kill that pid (usually
+you would do kill `cat twistd.pid`
).
+
+
+
As always, the gory details are in the manual page.
+
+
OS Integration
+
+
+If you have an Application that runs
+with twistd
, you can easily deploy it on
+RedHat Linux or Debian GNU/Linux based systems using
+the tap2deb
+or tap2rpm
tools. These take a Twisted
+Application file (of any of the supported formats â Python source, XML
+or pickle), and build a Debian or RPM package (respectively) that
+installs the Application as a system service. The package includes the
+Application file, a default /etc/init.d/
script that
+starts and stops the process with twistd, and post-installation
+scripts that configure the Application to be run in the appropriate
+init levels.
+
+
+
+
Note: tap2rpm
+and
tap2deb
do not package your entire
+application and dependent code, just the Twisted Application file. You
+will need to find some other way to package your Python code, such
+as
distutils '
+
bdist_rpm
command.
+
+
+
+For more savvy users, these tools also generate the source package, allowing
+you to modify and polish things which automated software cannot detect (such as
+dependencies or relationships to virtual packages).
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/book.tex b/doc/core/howto/book.tex
new file mode 100644
index 0000000..2708619
--- /dev/null
+++ b/doc/core/howto/book.tex
@@ -0,0 +1,129 @@
+\documentclass[oneside]{book}
+\usepackage[dvips]{graphicx}
+\usepackage{times,mathptmx}
+\usepackage{ifthen}
+\usepackage{hyperref}
+
+\usepackage{geometry}
+\geometry{verbose,letterpaper,tmargin=1in,bmargin=0.5in,lmargin=1in,rmargin=1in}
+
+\setlength{\oddsidemargin}{0in}
+\setlength{\textwidth}{\paperwidth}
+\addtolength{\textwidth}{-2in}
+
+\newcommand{\loreref}[1]{%
+ \ifthenelse{\value{page}=\pageref{#1}}%
+ { (this page)}%
+ { (page \pageref{#1})}%
+}
+
+
+\title{The Twisted Documentation}
+\author{The Twisted Development Team}
+
+\tolerance=1000
+\sloppy
+
+\begin{document}
+\maketitle
+\tableofcontents
+
+\chapter{Introduction}
+
+\input{vision.tex}
+
+
+\chapter{Getting Started}
+
+\input{servers.tex}
+\input{clients.tex}
+\input{trial.tex}
+\input{tutorial/index.tex}
+\input{tutorial/intro.tex}
+\input{tutorial/protocol.tex}
+\input{tutorial/style.tex}
+\input{tutorial/components.tex}
+\input{tutorial/backends.tex}
+\input{tutorial/web.tex}
+\input{tutorial/pb.tex}
+\input{tutorial/factory.tex}
+\input{tutorial/client.tex}
+\input{tutorial/library.tex}
+\input{tutorial/configuration.tex}
+\input{quotes.tex}
+\input{design.tex}
+
+
+\chapter{Networking and Other Event Sources}
+
+\input{internet-overview.tex}
+\input{reactor-basics.tex}
+\input{ssl.tex}
+\input{udp.tex}
+\input{process.tex}
+\input{defer.tex}
+\input{gendefer.tex}
+\input{time.tex}
+\input{threading.tex}
+\input{producers.tex}
+\input{choosing-reactor.tex}
+
+
+\chapter{High-Level Infrastructure}
+
+\input{endpoints.tex}
+\input{components.tex}
+\input{cred.tex}
+\input{plugin.tex}
+
+
+\chapter{Deploying Twisted Applications}
+
+\input{basics.tex}
+\input{application.tex}
+\input{tap.tex}
+
+
+\chapter{Utilities}
+
+\input{logging.tex}
+\input{constants.tex}
+\input{rdbms.tex}
+\input{options.tex}
+\input{dirdbm.tex}
+\input{testing.tex}
+
+
+\chapter{Asynchronous Messaging Protocol (AMP)}
+
+\input{amp.tex}
+
+
+\chapter{Perspective Broker}
+
+\input{pb.tex}
+\input{pb-intro.tex}
+\input{pb-usage.tex}
+\input{pb-clients.tex}
+\input{pb-copyable.tex}
+\input{pb-cred.tex}
+\input{pb-limits.tex}
+
+
+\chapter{Manual Pages}
+
+\input{../man/trial-man.tex}
+\clearpage
+\input{../man/twistd-man.tex}
+\clearpage
+\input{../man/tap2deb-man.tex}
+\clearpage
+\input{../man/tap2rpm-man.tex}
+
+
+\chapter{Appendix}
+
+\input{glossary.tex}
+\input{debug-with-emacs.tex}
+
+\end{document}
diff --git a/doc/core/howto/choosing-reactor.html b/doc/core/howto/choosing-reactor.html
new file mode 100644
index 0000000..22ea479
--- /dev/null
+++ b/doc/core/howto/choosing-reactor.html
@@ -0,0 +1,395 @@
+
+
+Twisted Documentation: Choosing a Reactor and GUI Toolkit Integration
+
+
+
+
+ Choosing a Reactor and GUI Toolkit Integration
+
+
+
+
+
Overview
+
+
Twisted provides a variety of implementations of the twisted.internet.reactor
. The specialized
+ implementations are suited for different purposes and are
+ designed to integrate better with particular platforms.
+
+
The epoll()-based reactor is Twisted's default on
+ Linux. Other platforms use poll() , or the most
+ cross-platform reactor, select() .
+
+
Platform-specific reactor implementations exist for:
+
+
+
+
The remaining custom reactor implementations provide support
+ for integrating with the native event loops of various graphical
+ toolkits. This lets your Twisted application use all of the
+ usual Twisted APIs while still being a graphical application.
+
+
Twisted currently integrates with the following graphical
+ toolkits:
+
+
+
+
When using applications that are runnable using twistd
, e.g.
+ TACs or plugins, there is no need to choose a reactor explicitly, since
+ this can be chosen using twistd
's -r option.
+
+
In all cases, the event loop is started by calling reactor.run()
. In all cases, the event loop
+ should be stopped with reactor.stop()
.
+
+
IMPORTANT: installing a reactor should be the first thing
+ done in the app, since any code that does
+ from twisted.internet import reactor
will automatically
+ install the default reactor if the code hasn't already installed one.
+
+
Reactor Functionality
+
+
+ Status TCP SSL UDP Threading Processes Scheduling Platforms
+ select() Stable Y Y Y Y Y Y Unix, Win32
+ poll Stable Y Y Y Y Y Y Unix
+ WaitForMultipleObjects (WFMO) for Win32 Experimental Y Y Y Y Y Y Win32
+ Input/Output Completion Port (IOCP) for Win32 Experimental Y Y Y Y Y Y Win32
+ CoreFoundation Unmaintained Y Y Y Y Y Y Mac OS X
+ epoll Stable Y Y Y Y Y Y Linux 2.6
+ GTK+ Stable Y Y Y Y Y Y Unix, Win32
+ wx Experimental Y Y Y Y Y Y Unix, Win32
+ kqueue Experimental Y Y Y Y Y Y FreeBSD
+
+
+
General Purpose Reactors
+
+
Select()-based Reactor
+
+
The select
reactor is the default on platforms that don't
+ provide a better alternative that covers all use cases. If
+ the select
reactor is desired, it may be installed via:
+
+
1
+2
+3
+4
+
from twisted .internet import selectreactor
+selectreactor .install ()
+
+from twisted .internet import reactor
+
+
+
Platform-Specific Reactors
+
+
Poll-based Reactor
+
+
The PollReactor will work on any platform that provides select.poll
. With larger numbers of connected
+ sockets, it may provide for better performance than the SelectReactor.
+
+
1
+2
+3
+4
+
from twisted .internet import pollreactor
+pollreactor .install ()
+
+from twisted .internet import reactor
+
+
+
KQueue
+
+
The KQueue Reactor allows Twisted to use FreeBSD's kqueue mechanism for
+ event scheduling. See instructions in the twisted.internet.kqreactor
's
+ docstring for installation notes.
+
+
1
+2
+3
+4
+
from twisted .internet import kqreactor
+kqreactor .install ()
+
+from twisted .internet import reactor
+
+
+
+
WaitForMultipleObjects (WFMO) for Win32
+
+
The Win32 reactor is not yet complete and has various limitations
+ and issues that need to be addressed. The reactor supports GUI integration
+ with the win32gui module, so it can be used for native Win32 GUI applications.
+
+
+
1
+2
+3
+4
+
from twisted .internet import win32eventreactor
+win32eventreactor .install ()
+
+from twisted .internet import reactor
+
+
+
Input/Output Completion Port (IOCP) for Win32
+
+
+ Windows provides a fast, scalable event notification system known as IO
+ Completion Ports, or IOCP for short. Twisted includes a reactor based
+ on IOCP which is nearly complete.
+
+
+
1
+2
+3
+4
+
from twisted .internet import iocpreactor
+iocpreactor .install ()
+
+from twisted .internet import reactor
+
+
+
Epoll-based Reactor
+
+
The EPollReactor will work on any platform that provides
+ epoll
, today only Linux 2.6 and over. The
+ implementation of the epoll reactor currently uses the Level Triggered
+ interface, which is basically like poll() but scales much better.
+
+
1
+2
+3
+4
+
from twisted .internet import epollreactor
+epollreactor .install ()
+
+from twisted .internet import reactor
+
+
+
GUI Integration Reactors
+
+
GTK+
+
+
Twisted integrates with PyGTK version
+ 2.0 using the gtk2reactor
. An example Twisted application that
+ uses GTK+ can be found
+ in doc/core/examples/pbgtk2.py
.
+
+
GTK-2.0 split the event loop out of the GUI toolkit and into a separate
+ module called glib . To run an application using the glib event loop,
+ use the glib2reactor
. This will be slightly faster
+ than gtk2reactor
(and does not require a working X display),
+ but cannot be used to run GUI applications.
+
+
1
+2
+3
+4
+
from twisted .internet import gtk2reactor
+gtk2reactor .install ()
+
+from twisted .internet import reactor
+
+
+
1
+2
+3
+4
+
from twisted .internet import glib2reactor
+glib2reactor .install ()
+
+from twisted .internet import reactor
+
+
+
GTK+ 3.0 and GObject Introspection
+
+
Twisted integrates with GTK+ 3 and GObject
+ through PyGObject's
+ introspection using the gtk3reactor
+ and gireactor
reactors.
+
+
1
+2
+3
+4
+
from twisted .internet import gtk3reactor
+gtk3reactor .install ()
+
+from twisted .internet import reactor
+
+
+
1
+2
+3
+4
+
from twisted .internet import gireactor
+gireactor .install ()
+
+from twisted .internet import reactor
+
+
+
GLib 3.0 introduces the concept of GApplication
, a class
+ that handles application uniqueness in a cross-platform way and provides
+ its own main loop. Its counterpart GtkApplication
also
+ handles application lifetime with respect to open windows. Twisted
+ supports registering these objects with the event loop, which should be
+ done before running the reactor:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
from twisted .internet import gtk3reactor
+gtk3reactor .install ()
+
+from gi .repository import Gtk
+app = Gtk .Application (...)
+
+from twisted import reactor
+reactor .registerGApplication (app )
+reactor .run ()
+
+
+
wxPython
+
+
Twisted currently supports two methods of integrating
+ wxPython. Unfortunately, neither method will work on all wxPython
+ platforms (such as GTK2 or Windows). It seems that the only
+ portable way to integrate with wxPython is to run it in a separate
+ thread. One of these methods may be sufficient if your wx app is
+ limited to a single platform.
+
+
As with Tkinter , the support for integrating
+ Twisted with a wxPython
+ application uses specialized support code rather than a simple reactor.
+
+
1
+2
+3
+4
+5
+
from wxPython .wx import *
+from twisted .internet import wxsupport , reactor
+
+myWxAppInstance = wxApp (0 )
+wxsupport .install (myWxAppInstance )
+
+
+
However, this has issues when running on Windows, so Twisted now
+ comes with alternative wxPython support using a reactor. Using
+ this method is probably better. Initialization is done in two
+ stages. In the first, the reactor is installed:
+
+
1
+2
+3
+4
+
from twisted .internet import wxreactor
+wxreactor .install ()
+
+from twisted .internet import reactor
+
+
+
Later, once a wxApp
instance has
+ been created, but before reactor.run()
+ is called:
+
+
1
+2
+3
+
from twisted .internet import reactor
+myWxAppInstance = wxApp (0 )
+reactor .registerWxApp (myWxAppInstance )
+
+
+
An example Twisted application that uses wxPython can be found
+ in doc/core/examples/wxdemo.py
.
+
+
CoreFoundation
+
+
Twisted integrates with PyObjC version 1.0. Sample applications using Cocoa and Twisted
+ are available in the examples directory under
+ doc/core/examples/threadedselect/Cocoa
.
+
+
1
+2
+3
+4
+
from twisted .internet import cfreactor
+cfreactor .install ()
+
+from twisted .internet import reactor
+
+
+
Non-Reactor GUI Integration
+
+
Tkinter
+
+
The support for Tkinter doesn't use a specialized reactor. Instead, there is
+ some specialized support code:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from Tkinter import *
+from twisted .internet import tksupport , reactor
+
+root = Tk ()
+
+
+tksupport .install (root )
+
+
+
+
+
+
+
PyUI
+
+
As with Tkinter , the support for integrating
+ Twisted with a PyUI
+ application uses specialized support code rather than a simple reactor.
+
+
1
+2
+3
+
from twisted .internet import pyuisupport , reactor
+
+pyuisupport .install (args =(640 , 480 ), kw ={'renderer' : 'gl' })
+
+
+
An example Twisted application that uses PyUI can be found in doc/core/examples/pyuidemo.py
.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/clients.html b/doc/core/howto/clients.html
new file mode 100644
index 0000000..024f32f
--- /dev/null
+++ b/doc/core/howto/clients.html
@@ -0,0 +1,741 @@
+
+
+Twisted Documentation: Writing Clients
+
+
+
+
+ Writing Clients
+
+
+
+
+
Overview
+
+
Twisted is a framework designed to be very flexible, and let you write
+ powerful clients. The cost of this flexibility is a few layers in the way
+ to writing your client. This document covers creating clients that can be
+ used for TCP, SSL and Unix sockets. UDP is covered in
+ a different document .
+
+
At the base, the place where you actually implement the protocol parsing
+ and handling, is the Protocol
class. This class will usually be
+ descended
+ from twisted.internet.protocol.Protocol
. Most
+ protocol handlers inherit either from this class or from one of its
+ convenience children. An instance of the protocol class will be instantiated
+ when you connect to the server and will go away when the connection is
+ finished. This means that persistent configuration is not saved in the
+ Protocol
.
+
+
The persistent configuration is kept in a Factory
+ class, which usually inherits from twisted.internet.protocol.Factory
+ (or twisted.internet.protocol.ClientFactory
: see
+ below). The default factory class just instantiates the Protocol
+ and then sets the protocol's factory
attribute to point to
+ itself (the factory). This lets the Protocol
access, and
+ possibly modify, the persistent configuration.
+
+
Protocol
+
+
As mentioned above, this and auxiliary classes and functions are where
+ most of the code is. A Twisted protocol handles data in an asynchronous
+ manner. This means that the protocol never waits for an event, but rather
+ responds to events as they arrive from the network.
+
+
Here is a simple example:
+
+
1
+2
+3
+4
+5
+6
+
from twisted .internet .protocol import Protocol
+from sys import stdout
+
+class Echo (Protocol ):
+ def dataReceived (self , data ):
+ stdout .write (data )
+
+
+
This is one of the simplest protocols. It just writes whatever it reads
+ from the connection to standard output. There are many events it does not
+ respond to. Here is an example of a Protocol
responding to
+ another event:
+
+
1
+2
+3
+4
+5
+6
+
from twisted .internet .protocol import Protocol
+
+class WelcomeMessage (Protocol ):
+ def connectionMade (self ):
+ self .transport .write ("Hello server, I am the client!\r\n" )
+ self .transport .loseConnection ()
+
+
+
This protocol connects to the server, sends it a welcome message, and
+ then terminates the connection.
+
+
The connectionMade
event is
+ usually where set up of the Protocol
object happens, as well as
+ any initial greetings (as in the
+ WelcomeMessage
protocol above). Any tearing down of
+ Protocol
-specific objects is done in connectionLost
.
+
+
Simple, single-use clients
+
+
In many cases, the protocol only needs to connect to the server once, and
+ the code just wants to get a connected instance of the protocol. In those
+ cases twisted.internet.endpoints
provides the
+ appropriate API.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
from twisted .internet import reactor
+from twisted .internet .protocol import Factory , Protocol
+from twisted .internet .endpoints import TCP4ClientEndpoint
+
+class Greeter (Protocol ):
+ def sendMessage (self , msg ):
+ self .transport .write ("MESSAGE %s\n" % msg )
+
+class GreeterFactory (Factory ):
+ def buildProtocol (self , addr ):
+ return Greeter ()
+
+def gotProtocol (p ):
+ p .sendMessage ("Hello" )
+ reactor .callLater (1 , p .sendMessage , "This is sent in a second" )
+ reactor .callLater (2 , p .transport .loseConnection )
+
+point = TCP4ClientEndpoint (reactor , "localhost" , 1234 )
+d = point .connect (GreeterFactory ())
+d .addCallback (gotProtocol )
+reactor .run ()
+
+
+
Regardless of the type of client endpoint, the way to set up a new
+ connection is simply to call the connect
method on it and pass
+ in a factory. This means it's easy to change the mechanism you're using to
+ connect, without changing the rest of your program. For example, to run
+ the greeter example over SSL, the only change required is to instantiate an
+ SSL4ClientEndpoint
instead of a
+ TCP4ClientEndpoint
. To take advantage of this, functions and
+ methods which initiates a new connection should generally accept an
+ endpoint as an argument and let the caller construct it, rather than taking
+ arguments like 'host' and 'port' and constructing its own before calling
+ connect
.
+
+
For more information on different ways you can make outgoing connections
+ to different types of endpoints, as well as parsing strings into endpoints,
+ see the documentation for the endpoints
+ API .
+
+
Note: If you've used ClientFactory
before,
+ make sure you remember that the connect
method takes a
+ Factory
, not a ClientFactory
. Even if you pass a
+ ClientFactory
to endpoint.connect
, its
+ clientConnectionFailed
and clientConnectionLost
+ methods will not be called.
+
+
You may come across code using ClientCreator
, an older API which is not as flexible as
+ the endpoint API. Rather than calling connect
on an endpoint,
+ such code will look like this:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .internet .protocol import ClientCreator
+
+...
+
+creator = ClientCreator (reactor , Greeter )
+d = creator .connectTCP ("localhost" , 1234 )
+d .addCallback (gotProtocol )
+reactor .run ()
+
+
+
In general, the endpoint API should be preferred in new code, as it lets
+ the caller select the method of connecting.
+
+
ClientFactory
+
+
Still, there's plenty of code out there that uses lower-level APIs, and
+ a few features (such as automatic reconnection) have not been
+ re-implemented with endpoints yet, so in some cases they may be more
+ convenient to use.
+
+
To use the lower-level connection APIs, you will need to call one of the
+ reactor.connect* methods directly. For these cases, you need a
+ ClientFactory
.
+ The ClientFactory
is in charge of creating the
+ Protocol
and also receives events relating to the connection
+ state. This allows it to do things like reconnect in the event of a
+ connection error. Here is an example of a simple ClientFactory
+ that uses the Echo
protocol (above) and also prints what state
+ the connection is in.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
from twisted .internet .protocol import Protocol , ClientFactory
+from sys import stdout
+
+class Echo (Protocol ):
+ def dataReceived (self , data ):
+ stdout .write (data )
+
+class EchoClientFactory (ClientFactory ):
+ def startedConnecting (self , connector ):
+ print 'Started to connect.'
+
+ def buildProtocol (self , addr ):
+ print 'Connected.'
+ return Echo ()
+
+ def clientConnectionLost (self , connector , reason ):
+ print 'Lost connection. Reason:' , reason
+
+ def clientConnectionFailed (self , connector , reason ):
+ print 'Connection failed. Reason:' , reason
+
+
+
To connect this EchoClientFactory
to a server, you could use
+ this code:
+
+
1
+2
+3
+
from twisted .internet import reactor
+reactor .connectTCP (host , port , EchoClientFactory ())
+reactor .run ()
+
+
+
Note that clientConnectionFailed
+ is called when a connection could not be established, and that clientConnectionLost
+ is called when a connection was made and then disconnected.
+
+
Reconnection
+
+
Often, the connection of a client will be lost unintentionally due to
+ network problems. One way to reconnect after a disconnection would be to
+ call connector.connect()
when the connection is lost:
+
+
1
+2
+3
+4
+5
+
from twisted .internet .protocol import ClientFactory
+
+class EchoClientFactory (ClientFactory ):
+ def clientConnectionLost (self , connector , reason ):
+ connector .connect ()
+
+
+
The connector passed as the first argument is the interface between a
+ connection and a protocol. When the connection fails and the factory
+ receives the clientConnectionLost
event, the factory can
+ call connector.connect()
to start the connection over again
+ from scratch.
+
+
+ However, most programs that want this functionality should
+ implement ReconnectingClientFactory
instead,
+ which tries to reconnect if a connection is lost or fails and which
+ exponentially delays repeated reconnect attempts.
+
+
+
+ Here is the Echo
protocol implemented with
+ a ReconnectingClientFactory
:
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
from twisted .internet .protocol import Protocol , ReconnectingClientFactory
+from sys import stdout
+
+class Echo (Protocol ):
+ def dataReceived (self , data ):
+ stdout .write (data )
+
+class EchoClientFactory (ReconnectingClientFactory ):
+ def startedConnecting (self , connector ):
+ print 'Started to connect.'
+
+ def buildProtocol (self , addr ):
+ print 'Connected.'
+ print 'Resetting reconnection delay'
+ self .resetDelay ()
+ return Echo ()
+
+ def clientConnectionLost (self , connector , reason ):
+ print 'Lost connection. Reason:' , reason
+ ReconnectingClientFactory .clientConnectionLost (self , connector , reason )
+
+ def clientConnectionFailed (self , connector , reason ):
+ print 'Connection failed. Reason:' , reason
+ ReconnectingClientFactory .clientConnectionFailed (self , connector ,
+ reason )
+
+
+
A Higher-Level Example: ircLogBot
+
+
Overview of ircLogBot
+
+
The clients so far have been fairly simple. A more complicated example
+ comes with Twisted Words in the doc/words/examples
+ directory.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+
+from twisted .words .protocols import irc
+from twisted .internet import reactor , protocol
+from twisted .python import log
+
+
+import time , sys
+
+
+class MessageLogger :
+ """
+ An independent logger class (because separation of application
+ and protocol logic is a good thing).
+ """
+ def __init__ (self , file ):
+ self .file = file
+
+ def log (self , message ):
+ """Write a message to the file."""
+ timestamp = time .strftime ("[%H:%M:%S]" , time .localtime (time .time ()))
+ self .file .write ('%s %s\n' % (timestamp , message ))
+ self .file .flush ()
+
+ def close (self ):
+ self .file .close ()
+
+
+class LogBot (irc .IRCClient ):
+ """A logging IRC bot."""
+
+ nickname = "twistedbot"
+
+ def connectionMade (self ):
+ irc .IRCClient .connectionMade (self )
+ self .logger = MessageLogger (open (self .factory .filename , "a" ))
+ self .logger .log ("[connected at %s]" %
+ time .asctime (time .localtime (time .time ())))
+
+ def connectionLost (self , reason ):
+ irc .IRCClient .connectionLost (self , reason )
+ self .logger .log ("[disconnected at %s]" %
+ time .asctime (time .localtime (time .time ())))
+ self .logger .close ()
+
+
+
+
+ def signedOn (self ):
+ """Called when bot has succesfully signed on to server."""
+ self .join (self .factory .channel )
+
+ def joined (self , channel ):
+ """This will get called when the bot joins the channel."""
+ self .logger .log ("[I have joined %s]" % channel )
+
+ def privmsg (self , user , channel , msg ):
+ """This will get called when the bot receives a message."""
+ user = user .split ('!' , 1 )[0 ]
+ self .logger .log ("<%s> %s" % (user , msg ))
+
+
+ if channel == self .nickname :
+ msg = "It isn't nice to whisper! Play nice with the group."
+ self .msg (user , msg )
+ return
+
+
+ if msg .startswith (self .nickname + ":" ):
+ msg = "%s: I am a log bot" % user
+ self .msg (channel , msg )
+ self .logger .log ("<%s> %s" % (self .nickname , msg ))
+
+ def action (self , user , channel , msg ):
+ """This will get called when the bot sees someone do an action."""
+ user = user .split ('!' , 1 )[0 ]
+ self .logger .log ("* %s %s" % (user , msg ))
+
+
+
+ def irc_NICK (self , prefix , params ):
+ """Called when an IRC user changes their nickname."""
+ old_nick = prefix .split ('!' )[0 ]
+ new_nick = params [0 ]
+ self .logger .log ("%s is now known as %s" % (old_nick , new_nick ))
+
+
+
+
+ def alterCollidedNick (self , nickname ):
+ """
+ Generate an altered version of a nickname that caused a collision in an
+ effort to create an unused related name for subsequent registration.
+ """
+ return nickname + '^'
+
+
+
+class LogBotFactory (protocol .ClientFactory ):
+ """A factory for LogBots.
+
+ A new protocol instance will be created each time we connect to the server.
+ """
+
+ def __init__ (self , channel , filename ):
+ self .channel = channel
+ self .filename = filename
+
+ def buildProtocol (self , addr ):
+ p = LogBot ()
+ p .factory = self
+ return p
+
+ def clientConnectionLost (self , connector , reason ):
+ """If we get disconnected, reconnect to server."""
+ connector .connect ()
+
+ def clientConnectionFailed (self , connector , reason ):
+ print "connection failed:" , reason
+ reactor .stop ()
+
+
+if __name__ == '__main__' :
+
+ log .startLogging (sys .stdout )
+
+
+ f = LogBotFactory (sys .argv [1 ], sys .argv [2 ])
+
+
+ reactor .connectTCP ("irc.freenode.net" , 6667 , f )
+
+
+ reactor .run ()
+
+
+
ircLogBot.py
connects to an IRC server, joins a channel, and
+ logs all traffic on it to a file. It demonstrates some of the
+ connection-level logic of reconnecting on a lost connection, as well as
+ storing persistent data in the Factory
.
+
+
Persistent Data in the Factory
+
+
Since the Protocol
instance is recreated each time the
+ connection is made, the client needs some way to keep track of data that
+ should be persisted. In the case of the logging bot, it needs to know which
+ channel it is logging, and where to log it.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
from twisted .words .protocols import irc
+from twisted .internet import protocol
+
+class LogBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ irc .IRCClient .connectionMade (self )
+ self .logger = MessageLogger (open (self .factory .filename , "a" ))
+ self .logger .log ("[connected at %s]" %
+ time .asctime (time .localtime (time .time ())))
+
+ def signedOn (self ):
+ self .join (self .factory .channel )
+
+
+class LogBotFactory (protocol .ClientFactory ):
+
+ def __init__ (self , channel , filename ):
+ self .channel = channel
+ self .filename = filename
+
+ def buildProtocol (self , addr ):
+ p = LogBot ()
+ p .factory = self
+ return p
+
+
+
When the protocol is created, it gets a reference to the factory as
+ self.factory
. It can then access attributes of the factory in
+ its logic. In the case of LogBot
, it opens the file and
+ connects to the channel stored in the factory.
+
+
Factories have a default implementation of buildProtocol
+ that does the same thing the example above does, using
+ the protocol
attribute of the factory to create the protocol
+ instance. In the example above, the factory could be rewritten to look
+ like this:
+
+
1
+2
+3
+4
+5
+6
+
class LogBotFactory (protocol .ClientFactory ):
+ protocol = LogBot
+
+ def __init__ (self , channel , filename ):
+ self .channel = channel
+ self .filename = filename
+
+
+
Further Reading
+
+
The Protocol
+ class used throughout this document is a base implementation
+ of IProtocol
+ used in most Twisted applications for convenience. To learn about the
+ complete IProtocol
interface, see the API documentation for
+ IProtocol
.
+
+
The transport
attribute used in some examples in this
+ document provides the ITCPTransport
interface. To learn
+ about the complete interface, see the API documentation
+ for ITCPTransport
.
+
+
Interface classes are a way of specifying what methods and attributes an
+ object has and how they behave. See the
+ Components: Interfaces and Adapters document for more information on
+ using interfaces in Twisted.
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/components.html b/doc/core/howto/components.html
new file mode 100644
index 0000000..b52e9e7
--- /dev/null
+++ b/doc/core/howto/components.html
@@ -0,0 +1,603 @@
+
+
+Twisted Documentation: Components: Interfaces and Adapters
+
+
+
+
+ Components: Interfaces and Adapters
+
+
+
+
+
Object oriented programming languages allow programmers to reuse portions of
+existing code by creating new classes of objects which subclass another
+class. When a class subclasses another, it is said to inherit all of its
+behaviour. The subclass can then override and extend the behavior
+provided to it by the superclass. Inheritance is very useful in many situations,
+but because it is so convenient to use, often becomes abused in large software
+systems, especially when multiple inheritance is involved. One solution is to
+use delegation instead of inheritance where appropriate.
+Delegation is simply the act of asking another object to perform a task
+for an object. To support this design pattern, which is often referred to as
+the components pattern because it involves many small interacting
+components, interfaces and adapters were created by the Zope
+3 team.
+
+
Interfaces are simply markers which objects can use to say I
+implement this interface . Other objects may then make requests like
+Please give me an object which implements interface X for object type Y .
+Objects which implement an interface for another object type are called
+adapters .
+
+
The superclass-subclass relationship is said to be an is-a relationship.
+When designing object hierarchies, object modellers use subclassing when they
+can say that the subclass is the same class as the superclass. For
+example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
class Shape :
+ sideLength = 0
+ def getSideLength (self ):
+ return self .sideLength
+
+ def setSideLength (self , sideLength ):
+ self .sideLength = sideLength
+
+ def area (self ):
+ raise NotImplementedError , "Subclasses must implement area"
+
+class Triangle (Shape ):
+ def area (self ):
+ return (self .sideLength * self .sideLength ) / 2
+
+class Square (Shape ):
+ def area (self ):
+ return self .sideLength * self .sideLength
+
+
+
In the above example, a Triangle is-a Shape, so it subclasses Shape,
+and a Square is-a Shape, so it also subclasses Shape.
+
+
However, subclassing can get complicated, especially when Multiple
+Inheritance enters the picture. Multiple Inheritance allows a class to inherit
+from more than one base class. Software which relies heavily on inheritance
+often ends up having both very wide and very deep inheritance trees, meaning
+that one class inherits from many superclasses spread throughout the system.
+Since subclassing with Multiple Inheritance means implementation
+inheritance , locating a method's actual implementation and ensuring the
+correct method is actually being invoked becomes a challenge. For example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
class Area :
+ sideLength = 0
+ def getSideLength (self ):
+ return self .sideLength
+
+ def setSideLength (self , sideLength ):
+ self .sideLength = sideLength
+
+ def area (self ):
+ raise NotImplementedError , "Subclasses must implement area"
+
+class Color :
+ color = None
+ def setColor (self , color ):
+ self .color = color
+
+ def getColor (self ):
+ return self .color
+
+class Square (Area , Color ):
+ def area (self ):
+ return self .sideLength * self .sideLength
+
+
+
The reason programmers like using implementation inheritance is because it
+makes code easier to read since the implementation details of Area are in a
+separate place than the implementation details of Color. This is nice, because
+conceivably an object could have a color but not an area, or an area but not a
+color. The problem, though, is that Square is not really an Area or a Color, but
+has an area and color. Thus, we should really be using another object oriented
+technique called composition , which relies on delegation rather than
+inheritance to break code into small reusable chunks. Let us continue with the
+Multiple Inheritance example, though, because it is often used in practice.
+
+
What if both the Color and the Area base class defined the same
+method, perhaps calculate
? Where would the implementation
+come from? The implementation that is located
+for Square().calculate()
depends on the method resolution
+order, or MRO, and can change when programmers change seemingly
+unrelated things by refactoring classes in other parts of the system,
+causing obscure bugs. Our first thought might be to change the
+calculate method name to avoid name clashes, to
+perhaps calculateArea
and calculateColor
.
+While explicit, this change could potentially require a large number
+of changes throughout a system, and is error-prone, especially when
+attempting to integrate two systems which you didn't write.
+
+
Let's imagine another example. We have an electric appliance, say a hair
+dryer. The hair dryer is American voltage. We have two electric sockets, one of
+them an American 120 Volt socket, and one of them a United Kingdom 240 Volt socket. If
+we plug the hair dryer into the 240 Volt socket, it is going to expect 120 Volt
+current and errors will result. Going back and changing the hair dryer to
+support both plug120Volt
and plug240Volt
methods would
+be tedious, and what if we decided we needed to plug the hair dryer into yet
+another type of socket? For example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+
class HairDryer :
+ def plug (self , socket ):
+ if socket .voltage () == 120 :
+ print "I was plugged in properly and am operating."
+ else :
+ print "I was plugged in improperly and "
+ print "now you have no hair dryer any more."
+
+class AmericanSocket :
+ def voltage (self ):
+ return 120
+
+class UKSocket :
+ def voltage (self ):
+ return 240
+
+
+
Given these classes, the following operations can be performed:
+
+
+>>> hd = HairDryer()
+>>> am = AmericanSocket()
+>>> hd.plug(am)
+I was plugged in properly and am operating.
+>>> uk = UKSocket()
+>>> hd.plug(uk)
+I was plugged in improperly and
+now you have no hair dryer any more.
+
+
+
We are going to attempt to solve this problem by writing an Adapter for
+the UKSocket
which converts the voltage for use with an American
+hair dryer. An Adapter is a class which is constructed with one and only one
+argument, the adaptee or original object. In this example, we
+will show all code involved for clarity:
+
+
1
+2
+3
+4
+5
+6
+
class AdaptToAmericanSocket :
+ def __init__ (self , original ):
+ self .original = original
+
+ def voltage (self ):
+ return self .original .voltage () / 2
+
+
+
Now, we can use it as so:
+
+
+>>> hd = HairDryer()
+>>> uk = UKSocket()
+>>> adapted = AdaptToAmericanSocket(uk)
+>>> hd.plug(adapted)
+I was plugged in properly and am operating.
+
+
+
So, as you can see, an adapter can 'override' the original implementation. It
+can also 'extend' the interface of the original object by providing methods the
+original object did not have. Note that an Adapter must explicitly delegate any
+method calls it does not wish to modify to the original, otherwise the Adapter
+cannot be used in places where the original is expected. Usually this is not a
+problem, as an Adapter is created to conform an object to a particular interface
+and then discarded.
+
+
Interfaces and Components in Twisted code
+
+
Adapters are a useful way of using multiple classes to factor code into
+discrete chunks. However, they are not very interesting without some more
+infrastructure. If each piece of code which wished to use an adapted object had
+to explicitly construct the adapter itself, the coupling between components
+would be too tight. We would like to achieve loose coupling , and this is
+where twisted.python.components
comes in.
+
+
First, we need to discuss Interfaces in more detail. As we mentioned
+earlier, an Interface is nothing more than a class which is used as a marker.
+Interfaces should be subclasses of zope.interface.Interface
, and
+have a very odd look to python programmers not used to them:
+
+
1
+2
+3
+4
+5
+6
+7
+
from zope .interface import Interface
+
+class IAmericanSocket (Interface ):
+ def voltage ():
+ """
+ Return the voltage produced by this socket object, as an integer.
+ """
+
+
+
Notice how it looks just like a regular class definition, other than
+inheriting from Interface
? However, the method definitions inside
+the class block do not have any method body! Since Python does not have any
+native language-level support for Interfaces like Java does, this is what
+distinguishes an Interface definition from a Class.
+
+
Now that we have a defined Interface, we can talk about objects using terms
+like this: The AmericanSocket
class implements the IAmericanSocket
interface and Please give me an object which
+adapts UKSocket
to the IAmericanSocket
+interface . We can make declarations about what interfaces a certain
+class implements, and we can request adapters which implement a certain
+interface for a specific class.
+
+
Let's look at how we declare that a class implements an interface:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from zope .interface import implements
+
+class AmericanSocket :
+
+ implements (IAmericanSocket )
+
+ def voltage (self ):
+ return 120
+
+
+
So, to declare that a class implements an interface, we simply
+call zope.interface.implements
at the class level.
+
+
Now, let's say we want to rewrite
+the AdaptToAmericanSocket
class as a real adapter. In
+this case we also specify it as
+implementing IAmericanSocket
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+
from zope .interface import implements
+
+class AdaptToAmericanSocket :
+
+ implements (IAmericanSocket )
+
+ def __init__ (self , original ):
+ """
+ Pass the original UKSocket object as original
+ """
+ self .original = original
+
+ def voltage (self ):
+ return self .original .voltage () / 2
+
+
+
Notice how we placed the implements declaration on this adapter class. So
+far, we have not achieved anything by using components other than requiring us
+to type more. In order for components to be useful, we must use the
+component registry . Since AdaptToAmericanSocket
+implements
+IAmericanSocket
and regulates the voltage of a
+UKSocket
object, we can register
+AdaptToAmericanSocket
as an IAmericanSocket
adapter
+for the UKSocket
class. It is easier to see how this is
+done in code than to describe it:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from zope .interface import Interface , implements
+from twisted .python import components
+
+class IAmericanSocket (Interface ):
+ def voltage ():
+ """Return the voltage produced by this socket object, as an integer.
+ """
+
+class AmericanSocket :
+ implements (IAmericanSocket )
+
+ def voltage (self ):
+ return 120
+
+class UKSocket :
+ def voltage (self ):
+ return 240
+
+class AdaptToAmericanSocket :
+
+ implements (IAmericanSocket )
+
+ def __init__ (self , original ):
+ self .original = original
+
+ def voltage (self ):
+ return self .original .voltage () / 2
+
+components .registerAdapter (
+ AdaptToAmericanSocket ,
+ UKSocket ,
+ IAmericanSocket )
+
+
+
Now, if we run this script in the interactive interpreter, we can discover a
+little more about how to use components. The first thing we can do is discover
+whether an object implements an interface or not:
+
+
+>>> IAmericanSocket.implementedBy(AmericanSocket)
+True
+>>> IAmericanSocket.implementedBy(UKSocket)
+False
+>>> am = AmericanSocket()
+>>> uk = UKSocket()
+>>> IAmericanSocket.providedBy(am)
+True
+>>> IAmericanSocket.providedBy(uk)
+False
+
+
+
As you can see, the AmericanSocket
instance claims to
+implement IAmericanSocket
, but the UKSocket
+does not. If we wanted to use the HairDryer
with the AmericanSocket
, we could know that it would be safe to do so by
+checking whether it implements IAmericanSocket
. However, if we
+decide we want to use HairDryer
with a UKSocket
+instance, we must adapt it to IAmericanSocket
before
+doing so. We use the interface object to do this:
+
+
+>>> IAmericanSocket(uk)
+<__main__.AdaptToAmericanSocket instance at 0x1a5120>
+
+
+
When calling an interface with an object as an argument, the interface
+looks in the adapter registry for an adapter which implements the interface for
+the given instance's class. If it finds one, it constructs an instance of the
+Adapter class, passing the constructor the original instance, and returns it.
+Now the HairDryer
can safely be used with the adapted UKSocket
. But what happens if we attempt to adapt an object
+which already implements IAmericanSocket
? We simply get back the
+original instance:
+
+
+>>> IAmericanSocket(am)
+<__main__.AmericanSocket instance at 0x36bff0>
+
+
+
So, we could write a new smart HairDryer
which
+automatically looked up an adapter for the socket you tried to plug it into:
+
+
1
+2
+3
+4
+5
+
class HairDryer :
+ def plug (self , socket ):
+ adapted = IAmericanSocket (socket )
+ assert adapted .voltage () == 120 , "BOOM"
+ print "I was plugged in properly and am operating"
+
+
+
Now, if we create an instance of our new smart HairDryer
+and attempt to plug it in to various sockets, the HairDryer
will
+adapt itself automatically depending on the type of socket it is plugged in
+to:
+
+
+>>> am = AmericanSocket()
+>>> uk = UKSocket()
+>>> hd = HairDryer()
+>>> hd.plug(am)
+I was plugged in properly and am operating
+>>> hd.plug(uk)
+I was plugged in properly and am operating
+
+
+
Voila; the magic of components.
+
+
Components and Inheritance
+
+
If you inherit from a class which implements some interface, and your new
+subclass declares that it implements another interface, the implements will be
+inherited by default.
+
+
For example, pb.Root
is a class
+which implements IPBRoot
. This interface indicates that an
+object has remotely-invokable methods and can be used as the initial object
+served by a new Broker instance. It has an implements
setting
+like:
+
+
1
+2
+3
+4
+
from zope .interface import implements
+
+class Root (Referenceable ):
+ implements (IPBRoot )
+
+
+
Suppose you have your own class which implements your
+IMyInterface
interface:
+
+
1
+2
+3
+4
+5
+6
+7
+
from zope .interface import implements , Interface
+
+class IMyInterface (Interface ):
+ pass
+
+class MyThing :
+ implements (IMyInterface )
+
+
+
Now if you want to make this class inherit from pb.Root
,
+the interfaces code will automatically determine that it also implements
+IPBRoot
:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .spread import pb
+from zope .interface import implements , Interface
+
+class IMyInterface (Interface ):
+ pass
+
+class MyThing (pb .Root ):
+ implements (IMyInterface )
+
+
+
+>>> from twisted.spread.flavors import IPBRoot
+>>> IPBRoot.implementedBy(MyThing)
+True
+
+
+
If you want MyThing
to inherit from pb.Root
but not implement IPBRoot
like pb.Root
does,
+use implementOnly
:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .spread import pb
+from zope .interface import implementsOnly , Interface
+
+class IMyInterface (Interface ):
+ pass
+
+class MyThing (pb .Root ):
+ implementsOnly (IMyInterface )
+
+
+
+>>> from twisted.spread.pb import IPBRoot
+>>> IPBRoot.implementedBy(MyThing)
+False
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/constants.html b/doc/core/howto/constants.html
new file mode 100644
index 0000000..ede0af0
--- /dev/null
+++ b/doc/core/howto/constants.html
@@ -0,0 +1,456 @@
+
+
+Twisted Documentation: Symbolic Constants
+
+
+
+
+ Symbolic Constants
+
+
+
+
+
Overview
+
+
It is often useful to define names which will be treated as
+ constants. twisted.python.constants
provides APIs
+ for defining such symbolic constants with minimal overhead and some useful
+ features beyond those afforded by the common Python idioms for this task.
+
+
This document will explain how to use these APIs and what circumstances
+ they might be helpful in.
+
+
Constant Names
+
+
Constants which have no value apart from their name and identity can be
+ defined by subclassing Names
.
+ Consider this example, in which some HTTP request method constants are defined.
+
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
from twisted .python .constants import NamedConstant , Names
+class METHOD (Names ):
+ """
+ Constants representing various HTTP request methods.
+ """
+ GET = NamedConstant ()
+ PUT = NamedConstant ()
+ POST = NamedConstant ()
+ DELETE = NamedConstant ()
+
+
+
Only direct subclasses of Names
are supported (i.e., you
+ cannot subclass METHOD
to add new constants the collection).
+
+
Given this definition, constants can be looked up by name using attribute
+ access on the METHOD
object:
+
+
+>>> METHOD.GET
+<METHOD=GET>
+>>> METHOD.PUT
+<METHOD=PUT>
+>>>
+
+
+
If it's necessary to look up constants based on user input of some sort, a
+ safe way to do it is using lookupByName
:
+
+
+>>> METHOD.lookupByName('GET')
+<METHOD=GET>
+>>> METHOD.lookupByName('__doc__')
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "twisted/python/constants.py", line 145, in lookupByName
+ raise ValueError(name)
+ValueError: __doc__
+>>>
+
+
+
As demonstrated, it is safe because any name not associated with a constant
+ (even those special names initialized by Python itself) will result
+ in ValueError
being raised, not some other object not intended to
+ be used the way the constants are used.
+
+
The constants can also be enumerated using the iterconstants
+ method.
+
+
+>>> list(METHOD.iterconstants())
+[<METHOD=GET>, <METHOD=PUT>, <METHOD=POST>, <METHOD=DELETE>]
+>>>
+
+
+
And constants can also be compared, either for equality or identity:
+
+
+>>> METHOD.GET is METHOD.GET
+True
+>>> METHOD.GET == METHOD.GET
+True
+>>> METHOD.GET is METHOD.PUT
+False
+>>> METHOD.GET == METHOD.PUT
+False
+>>>
+
+
+
Custom functionality can also be associated with constants defined this
+ way. A subclass of Names
may define class methods to implement
+ such functionality. Consider this redefinition of METHOD
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
from twisted .python .constants import NamedConstant , Names
+class METHOD (Names ):
+ """
+ Constants representing various HTTP request methods.
+ """
+ GET = NamedConstant ()
+ PUT = NamedConstant ()
+ POST = NamedConstant ()
+ DELETE = NamedConstant ()
+
+ @classmethod
+ def isIdempotent (cls , method ):
+ """
+ Return True if the given method is side-effect free, False otherwise.
+ """
+ return method is cls .GET
+
+
+
This functionality can be used as any class methods are used:
+
+
+>>> METHOD.isIdempotent(METHOD.GET)
+True
+>>> METHOD.isIdempotent(METHOD.POST)
+False
+>>>
+
+
+
Constants With Values
+
+
Constants with a particular associated value are supported by
+ the Values
base
+ class. Consider this example, in which some HTTP status code constants are
+ defined.
+
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .python .constants import ValueConstant , Values
+class STATUS (Values ):
+ """
+ Constants representing various HTTP status codes.
+ """
+ OK = ValueConstant ("200" )
+ FOUND = ValueConstant ("302" )
+ NOT_FOUND = ValueConstant ("404" )
+
+
+
As with Names
, constants are accessed as attributes of the
+ class object:
+
+
+>>> STATUS.OK
+<STATUS=OK>
+>>> STATUS.FOUND
+<STATUS=FOUND>
+>>>
+
+
+
Additionally, the values of the constants can be accessed using
+ the value
attribute of one these objects:
+
+
+>>> STATUS.OK.value
+'200'
+>>>
+
+
+
And as with Names
, constants can be looked up by name:
+
+
+>>> STATUS.lookupByName('NOT_FOUND')
+<STATUS=NOT_FOUND>
+>>>
+
+
+
Constants on a Values
subclass can also be looked up by
+ value:
+
+
+>>> STATUS.lookupByValue('404')
+<STATUS=NOT_FOUND>
+>>> STATUS.lookupByValue('500')
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "twisted/python/constants.py", line 244, in lookupByValue
+ raise ValueError(value)
+ValueError: 500
+>>>
+
+
+
Multiple constants may have the same value. If they do,
+ lookupByValue
will find the one which is defined first.
+
+
Iteration is also supported:
+
+
+>>> list(STATUS.iterconstants())
+[<STATUS=OK>, <STATUS=FOUND>, <STATUS=NOT_FOUND>]
+>>>
+
+
+
And constants can be compared for equality and identity:
+
+
+>>> STATUS.OK == STATUS.OK
+True
+>>> STATUS.OK is STATUS.OK
+True
+>>> STATUS.OK == STATUS.OK
+True
+>>> STATUS.OK is STATUS.NOT_FOUND
+False
+>>> STATUS.OK == STATUS.NOT_FOUND
+False
+>>>
+
+
+
And, as with Names
, a subclass of Values
can
+ define methods:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+
from twisted .python .constants import ValueConstant , Values
+class STATUS (Values ):
+ """
+ Constants representing various HTTP status codes.
+ """
+ OK = ValueConstant ("200" )
+ NO_CONTENT = ValueConstant ("204" )
+ NOT_MODIFIED = ValueConstant ("304" )
+ NOT_FOUND = ValueConstant ("404" )
+
+ @classmethod
+ def hasBody (cls , status ):
+ """
+ Return True if the given status is associated with a response body,
+ False otherwise.
+ """
+ return status in (cls .NO_CONTENT , cls .NOT_MODIFIED )
+
+
+
This functionality can be used as any class methods are used:
+
+
+>>> STATUS.hasBody(STATUS.OK)
+True
+>>> STATUS.hasBody(STATUS.NO_CONTENT)
+False
+>>>
+
+
+
Constants As Flags
+
+
Integers are often used as a simple set for constants. The values for
+ these constants are assigned as powers of two so that bits in the integer can
+ be set to represent them. Individual bits are often called flags .
+ Flags
supports this
+ use-case, including allowing constants with particular bits to be set, for
+ interoperability with other tools.
+
+
POSIX filesystem access control is traditionally done using a bitvector
+ defining which users and groups may perform which operations on a file. This
+ state might be represented using Flags
as follows:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+
from twisted .python .constants import FlagConstant , Flags
+class Permission (Flags ):
+ """
+ Constants representing user, group, and other access bits for reading,
+ writing, and execution.
+ """
+ OTHER_EXECUTE = FlagConstant ()
+ OTHER_WRITE = FlagConstant ()
+ OTHER_READ = FlagConstant ()
+ GROUP_EXECUTE = FlagConstant ()
+ GROUP_WRITE = FlagConstant ()
+ GROUP_READ = FlagConstant ()
+ USER_EXECUTE = FlagConstant ()
+ USER_WRITE = FlagConstant ()
+ USER_READ = FlagConstant ()
+
+
+
+ As for the previous types of constants, these can be accessed as attributes
+ of the class object:
+
+
+
+>>> Permission.USER_READ
+<Permission=USER_READ>
+>>> Permission.USER_WRITE
+<Permission=USER_WRITE>
+>>> Permission.USER_EXECUTE
+<Permission=USER_EXECUTE>
+>>>
+
+
+
These constant objects also have a value
attribute giving
+ their integer value:
+
+
+>>> Permission.USER_READ.value
+256
+>>>
+
+
+
And these constants can be looked up by name or value:
+
+
+>>> Permission.lookupByName('USER_READ') is Permission.USER_READ
+True
+>>> Permission.lookupByValue(256) is Permission.USER_READ
+True
+>>>
+
+
+
Constants can also be combined using the logical operators &
+ (and ), |
(or ), and ^
+ (exclusive or ).
+
+
+
+>>> Permission.USER_READ | Permission.USER_WRITE
+<Permission={USER_READ,USER_WRITE}>
+>>> (Permission.USER_READ | Permission.USER_WRITE) & Permission.USER_WRITE
+<Permission=USER_WRITE>
+>>> (Permission.USER_READ | Permission.USER_WRITE) ^ Permission.USER_WRITE
+<Permission=USER_READ>
+>>>
+
+
+
The unary operator ~
(not ) is also defined:
+
+
+>>> ~Permission.USER_READ
+<Permission={GROUP_EXECUTE,GROUP_READ,GROUP_WRITE,OTHER_EXECUTE,OTHER_READ,OTHER_WRITE,USER_EXECUTE,USER_WRITE}>
+>>>
+
+
+
Constants created using these operators also have a value
+ attribute.
+
+
+>>> (~Permission.USER_WRITE).value
+383
+>>>
+
+
+
+ Note the care taken to ensure the ~
operator is applied first
+ and the value
attribute is looked up second.
+
+
+
A Flags
subclass can also define methods, just as
+ a Names
or Values
subclass may. For example,
+ Permission
might benefit from a method to format a flag as a
+ string in the traditional style. Consider this addition to that class:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .python import filepath
+from twisted .python .constants import FlagConstant , Flags
+class Permission (Flags ):
+ ...
+
+ @classmethod
+ def format (cls , permissions ):
+ """
+ Format permissions flags in the traditional 'rwxr-xr-x' style.
+ """
+ return filepath .Permissions (permissions .value ).shorthand ()
+
+
+
Use this like any other class method:
+
+
+>>> Permission.format(Permission.USER_READ | Permission.USER_WRITE | Permission.GROUP_READ | Permission.OTHER_READ)
+'rw-r--r--'
+>>>
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/cred.html b/doc/core/howto/cred.html
new file mode 100644
index 0000000..44b4822
--- /dev/null
+++ b/doc/core/howto/cred.html
@@ -0,0 +1,566 @@
+
+
+Twisted Documentation: Cred: Pluggable Authentication
+
+
+
+
+ Cred: Pluggable Authentication
+
+
+
+
+
Goals
+
+
Cred is a pluggable authentication system for servers. It allows any
+number of network protocols to connect and authenticate to a system, and
+communicate to those aspects of the system which are meaningful to the specific
+protocol. For example, Twisted's POP3 support passes a username and
+password set of credentials to get back a mailbox for the specified email
+account. IMAP does the same, but retrieves a slightly different view of the
+same mailbox, enabling those features specific to IMAP which are not available
+in other mail protocols.
+
+
Cred is designed to allow both the backend implementation of the business
+logic - called the avatar - and the authentication database - called
+the credential checker - to be decided during deployment. For example,
+the same POP3 server should be able to authenticate against the local UNIX
+password database or an LDAP server without having to know anything about how
+or where mail is stored.
+
+
To sketch out how this works - a Realm corresponds to an application
+domain and is in charge of avatars, which are network-accessible business logic
+objects. To connect this to an authentication database, a top-level object
+called a Portal
stores a
+realm, and a number of credential checkers. Something that wishes to log in,
+such as a Protocol
,
+stores a reference to the portal. Login consists of passing credentials and a
+request interface (e.g. POP3's IMailbox
) to the portal. The portal passes
+the credentials to the appropriate credential checker, which returns an avatar
+ID. The ID is passed to the realm, which returns the appropriate avatar. For a
+Portal that has a realm that creates mailbox objects and a credential checker
+that checks /etc/passwd, login consists of passing in a username/password and
+the IMailbox interface to the portal. The portal passes this to the /etc/passwd
+credential checker, gets back a avatar ID corresponding to an email account,
+passes that to the realm and gets back a mailbox object for that email
+account.
+
+
Putting all this together, here's how a login request will typically be
+processed:
+
+
+
+
Cred objects
+
The Portal
+
This is the the core of login, the point of integration between all the objects
+in the cred system. There is one
+concrete implementation of Portal, and no interface - it does a very
+simple task. A Portal
+associates one (1) Realm with a collection of
+CredentialChecker instances. (More on those later.)
+
+
If you are writing a protocol that needs to authenticate against
+something, you will need a reference to a Portal, and to nothing else.
+This has only 2 methods -
+
+
+login
(credentials, mind, *interfaces)
+
+The docstring is quite expansive (see twisted.cred.portal
), but in
+brief, this is what you call when you need to call in order to connect
+a user to the system. Typically you only pass in one interface, and the mind
+is None
. The interfaces are the possible interfaces the returned
+avatar is expected to implement, in order of preference.
+The result is a deferred which fires a tuple of:
+
+ interface the avatar implements (which was one of the interfaces passed in the *interfaces
+tuple)
+ an object that implements that interface (an avatar)
+ logout, a 0-argument callable which disconnects the connection that was
+established by this call to login
+
+The logout method has to be called when the avatar is logged out. For POP3 this means
+when the protocol is disconnected or logged out, etc..
+
+registerChecker
(checker, *credentialInterfaces)
+
+which adds a CredentialChecker to the portal. The optional list of interfaces are interfaces of credentials
+that the checker is able to check.
+
+
+
The CredentialChecker
+
+
This is an object implementing ICredentialsChecker
which resolves some
+credentials to an avatar ID.
+
+Whether the credentials are stored in an in-memory data structure, an
+Apache-style htaccess file, a UNIX password database, an SSH key database,
+or any other form, an implementation of ICredentialsChecker
is
+how this data is connected to cred.
+
+A credential checker
+stipulates some requirements of the credentials it can check by
+specifying a credentialInterfaces attribute, which is a list of
+interfaces. Credentials passed to its requestAvatarId method must
+implement one of those interfaces.
+
+
For the most part, these things will just check usernames and passwords
+and produce the username as the result, but hopefully we will be seeing
+some public-key, challenge-response, and certificate based credential
+checker mechanisms soon.
+
+
A credential checker should raise an error if it cannot authenticate
+the user, and return twisted.cred.checkers.ANONYMOUS
+for anonymous access.
+
+
The Credentials
+
Oddly enough, this represents some credentials that the user presents.
+Usually this will just be a small static blob of data, but in some
+cases it will actually be an object connected to a network protocol.
+For example, a username/password pair is static, but a
+challenge/response server is an active state-machine that will require
+several method calls in order to determine a result.
+
+
Twisted comes with a number of credentials interfaces and implementations
+in the twisted.cred.credentials
module,
+such as IUsernamePassword
+and IUsernameHashedPassword
.
+
+
The Realm
+
A realm is an interface which connects your universe of business
+objects to the authentication system.
+
+
IRealm
is another one-method interface:
+
+
+requestAvatar
(avatarId, mind, *interfaces)
+
+This method will typically be called from 'Portal.login'. The avatarId
+is the one returned by a CredentialChecker.
+
+Note: Note that avatarId
must always be a string. In
+particular, do not use unicode strings. If internationalized support is needed,
+it is recommended to use UTF-8, and take care of decoding in the realm.
+
+The important thing to realize about this method is that if it is being
+called, the user has already authenticated . Therefore, if possible,
+the Realm should create a new user if one does not already exist
+whenever possible. Of course, sometimes this will be impossible
+without more information, and that is the case that the interfaces
+argument is for.
+
+
+
+
Since requestAvatar should be called from a Deferred callback, it may
+return a Deferred or a synchronous result.
+
+
The Avatar
+
+
An avatar is a business logic object for a specific user. For POP3, it's
+a mailbox, for a first-person-shooter it's the object that interacts with
+the game, the actor as it were. Avatars are specific to an application,
+and each avatar represents a single user .
+
+
The Mind
+
+
As mentioned before, the mind is usually None
, so you can skip this
+bit if you want.
+
+
Masters of Perspective Broker already know this object as the ill-named
+client object . There is no mind class, or even interface, but it
+is an object which serves an important role - any notifications which are to be
+relayed to an authenticated client are passed through a 'mind'. In addition, it
+allows passing more information to the realm during login in addition to the
+avatar ID.
+
+
The name may seem rather unusual, but considering that a Mind is
+representative of the entity on the other end of a network connection
+that is both receiving updates and issuing commands, I believe it is
+appropriate.
+
+
Although many protocols will not use this, it serves an important role.
+ It is provided as an argument both to the Portal and to the Realm,
+although a CredentialChecker should interact with a client program
+exclusively through a Credentials instance.
+
+
Unlike the original Perspective Broker client object , a Mind's
+implementation is most often dictated by the protocol that is
+connecting rather than the Realm. A Realm which requires a particular
+interface to issue notifications will need to wrap the Protocol's mind
+implementation with an adapter in order to get one that conforms to its
+expected interface - however, Perspective Broker will likely continue
+to use the model where the client object has a pre-specified remote
+interface.
+
+
(If you don't quite understand this, it's fine. It's hard to explain,
+and it's not used in simple usages of cred, so feel free to pass None
+until you find yourself requiring something like this.)
+
+
Responsibilities
+
+
Server protocol implementation
+
+
The protocol implementor should define the interface the avatar should implement,
+and design the protocol to have a portal attached. When a user logs in using the
+protocol, a credential object is created, passed to the portal, and an avatar
+with the appropriate interface is requested. When the user logs out or the protocol
+is disconnected, the avatar should be logged out.
+
+
The protocol designer should not hardcode how users are authenticated or the
+realm implemented. For example, a POP3 protocol implementation would require a portal whose
+realm returns avatars implementing IMailbox and whose credential checker accepts
+username/password credentials, but that is all. Here's a sketch of how the code
+might look - note that USER and PASS are the protocol commands used to login, and
+the DELE command can only be used after you are logged in:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+
from zope .interface import Interface
+
+from twisted .protocols import basic
+from twisted .python import log
+from twisted .cred import credentials , error
+from twisted .internet import defer
+
+class IMailbox (Interface ):
+ """Interface specification for mailbox."""
+ def deleteMessage (index ): pass
+
+
+class POP3 (basic .LineReceiver ):
+
+ def __init__ (self , portal ):
+ self .portal = portal
+
+ def do_DELE (self , i ):
+
+ i = int (i )-1
+ self .mbox .deleteMessage (i )
+ self .successResponse ()
+
+ def do_USER (self , user ):
+ self ._userIs = user
+ self .successResponse ('USER accepted, send PASS' )
+
+ def do_PASS (self , password ):
+ if self ._userIs is None :
+ self .failResponse ("USER required before PASS" )
+ return
+ user = self ._userIs
+ self ._userIs = None
+ d = defer .maybeDeferred (self .authenticateUserPASS , user , password )
+ d .addCallback (self ._cbMailbox , user )
+
+ def authenticateUserPASS (self , user , password ):
+ if self .portal is not None :
+ return self .portal .login (
+ cred .credentials .UsernamePassword (user , password ),
+ None ,
+ IMailbox
+ )
+ raise error .UnauthorizedLogin ()
+
+ def _cbMailbox (self , ial , user ):
+ interface , avatar , logout = ial
+
+ if interface is not IMailbox :
+ self .failResponse ('Authentication failed' )
+ log .err ("_cbMailbox() called with an interface other than IMailbox" )
+ return
+
+ self .mbox = avatar
+ self ._onLogout = logout
+ self .successResponse ('Authentication succeeded' )
+ log .msg ("Authenticated login for " + user )
+
+
+
Application implementation
+
+
The application developer can implement realms and credential checkers. For example,
+she might implement a realm that returns IMailbox implementing avatars, using MySQL
+for storage, or perhaps a credential checker that uses LDAP for authentication.
+In the following example, the Realm for a simple remote object service (using
+Twisted's Perspective Broker protocol) is implemented:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
from twisted .spread import pb
+from twisted .cred .portal import IRealm
+
+class SimplePerspective (pb .Avatar ):
+
+ def perspective_echo (self , text ):
+ print 'echoing' ,text
+ return text
+
+ def logout (self ):
+ print self , "logged out"
+
+
+class SimpleRealm :
+ implements (IRealm )
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective in interfaces :
+ avatar = SimplePerspective ()
+ return pb .IPerspective , avatar , avatar .logout
+ else :
+ raise NotImplementedError ("no interface" )
+
+
+
Deployment
+
+
Deployment involves tying together a protocol, an appropriate realm and a credential
+checker. For example, a POP3 server can be constructed by attaching to it a portal
+that wraps the MySQL-based realm and an /etc/passwd credential checker, or perhaps
+the LDAP credential checker if that is more useful. The following example shows
+how the SimpleRealm in the previous example is deployed using an in-memory credential checker:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .cred .portal import Portal
+from twisted .cred .checkers import InMemoryUsernamePasswordDatabaseDontUse
+
+portal = Portal (SimpleRealm ())
+checker = InMemoryUsernamePasswordDatabaseDontUse ()
+checker .addUser ("guest" , "password" )
+portal .registerChecker (checker )
+reactor .listenTCP (9986 , pb .PBServerFactory (portal ))
+reactor .run ()
+
+
+
Cred plugins
+
+
Authentication with cred plugins
+
+
Cred offers a plugin architecture for authentication methods. The
+primary API for this architecture is the command-line; the plugins are
+meant to be specified by the end-user when deploying a TAP (twistd
+plugin).
+
+
For more information on writing a twistd plugin and using cred
+plugins for your application, please refer to the Writing a twistd plugin document.
+
+
Building a cred plugin
+
+
To build a plugin for cred, you should first define an authType
, a short one-word string that defines
+your plugin to the command-line. Once you have this, the convention is
+to create a file named myapp_plugins.py
in the
+twisted.plugins
module path.
+
+
Below is an example file structure for an application that defines
+such a plugin:
+
+
+MyApplication/
+
+ setup.py
+ myapp/
+
+ __init__.py
+ cred.py
+ server.py
+
+
+ twisted/
+
+
+
+
+
+
+
+Once you have created this structure within your application, you can
+create the code for your cred plugin by building a factory class which
+implements ICheckerFactory
.
+These factory classes should not consist of a tremendous amount of
+code. Most of the real application logic should reside in the cred
+checker itself. (For help on building those, scroll up.)
+
+
+
+The core purpose of the CheckerFactory is to translate an argstring
, which is passed on the command line,
+into a suitable set of initialization parameters for a Checker
+class. In most cases this should be little more than constructing a
+dictionary or a tuple of arguments, then passing them along to a new
+checker instance.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from zope .interface import implements
+
+from twisted import plugin
+from twisted .cred .strcred import ICheckerFactory
+from myapp .cred import SpecialChecker
+
+class SpecialCheckerFactory (object ):
+ """
+ A checker factory for a specialized (fictional) API.
+ """
+
+
+ implements (ICheckerFactory , plugin .IPlugin )
+
+
+ authType = "special"
+
+
+
+ argStringFormat = "A colon-separated key=value list."
+
+
+
+ authHelp = """Some help text goes here ..."""
+
+
+ def generateChecker (self , argstring ="" ):
+ argdict = dict ((x .split ('=' ) for x in argstring .split (':' )))
+ return SpecialChecker (**dict )
+
+
+theSpecialCheckerFactory = SpecialCheckerFactory ()
+
+
+
For more information on how your plugin can be used in your
+application (and by other application developers), please see the Writing a twistd plugin document.
+
+
Conclusion
+
+
After reading through this tutorial, you should be able to
+
+
+Understand how the cred architecture applies to your application
+Integrate your application with cred's object model
+Deploy an application that uses cred for authentication
+Allow your users to use command-line authentication plugins
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/debug-with-emacs.html b/doc/core/howto/debug-with-emacs.html
new file mode 100644
index 0000000..48a2a4d
--- /dev/null
+++ b/doc/core/howto/debug-with-emacs.html
@@ -0,0 +1,65 @@
+
+
+Twisted Documentation: Debugging Python(Twisted) with Emacs
+
+
+
+
+ Debugging Python(Twisted) with Emacs
+
+
+
+
+
+
1
+
+
+
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/defer.html b/doc/core/howto/defer.html
new file mode 100644
index 0000000..492175b
--- /dev/null
+++ b/doc/core/howto/defer.html
@@ -0,0 +1,898 @@
+
+
+Twisted Documentation: Deferred Reference
+
+
+
+
+ Deferred Reference
+
+
+
+
+
+
This document is a guide to the behaviour of the twisted.internet.defer.Deferred
object, and to various
+ways you can use them when they are returned by functions.
+
+
This document assumes that you are familiar with the basic principle that
+the Twisted framework is structured around: asynchronous, callback-based
+programming, where instead of having blocking code in your program or using
+threads to run blocking code, you have functions that return immediately and
+then begin a callback chain when data is available.
+
+
+After reading this document, the reader should expect to be able to
+deal with most simple APIs in Twisted and Twisted-using code that
+return Deferreds.
+
+
+
+what sorts of things you can do when you get a Deferred from a
+function call; and
+how you can write your code to robustly handle errors in Deferred
+code.
+
+
+
+
Deferreds
+
+
Twisted uses the Deferred
object to manage the callback
+sequence. The client application attaches a series of functions to the
+deferred to be called in order when the results of the asychronous request are
+available (this series of functions is known as a series of
+callbacks , or a callback chain ), together
+with a series of functions to be called if there is an error in the
+asychronous request (known as a series of errbacks or an errback chain ). The asychronous library code calls the first
+callback when the result is available, or the first errback when an error
+occurs, and the Deferred
object then hands the results of each
+callback or errback function to the next function in the chain.
+
+
Callbacks
+
+
A twisted.internet.defer.Deferred
is a promise that
+a function will at some point have a result. We can attach callback functions
+to a Deferred, and once it gets a result these callbacks will be called. In
+addition Deferreds allow the developer to register a callback for an error,
+with the default behavior of logging the error. The deferred mechanism
+standardizes the application programmer's interface with all sorts of
+blocking or delayed operations.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
from twisted .internet import reactor , defer
+
+def getDummyData (x ):
+ """
+ This function is a dummy which simulates a delayed result and
+ returns a Deferred which will fire with that result. Don't try too
+ hard to understand this.
+ """
+ d = defer .Deferred ()
+
+
+ reactor .callLater (2 , d .callback , x * 3 )
+ return d
+
+def printData (d ):
+ """
+ Data handling function to be added as a callback: handles the
+ data by printing the result
+ """
+ print d
+
+d = getDummyData (3 )
+d .addCallback (printData )
+
+
+
+reactor .callLater (4 , reactor .stop )
+
+reactor .run ()
+
+
+
Multiple callbacks
+
+
Multiple callbacks can be added to a Deferred. The first callback in the
+Deferred's callback chain will be called with the result, the second with the
+result of the first callback, and so on. Why do we need this? Well, consider
+a Deferred returned by twisted.enterprise.adbapi - the result of a SQL query.
+A web widget might add a callback that converts this result into HTML, and
+pass the Deferred onwards, where the callback will be used by twisted to
+return the result to the HTTP client. The callback chain will be bypassed in
+case of errors or exceptions.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+
from twisted .internet import reactor , defer
+
+class Getter :
+ def gotResults (self , x ):
+ """
+ The Deferred mechanism provides a mechanism to signal error
+ conditions. In this case, odd numbers are bad.
+
+ This function demonstrates a more complex way of starting
+ the callback chain by checking for expected results and
+ choosing whether to fire the callback or errback chain
+ """
+ if x % 2 == 0 :
+ self .d .callback (x *3 )
+ else :
+ self .d .errback (ValueError ("You used an odd number!" ))
+
+ def _toHTML (self , r ):
+ """
+ This function converts r to HTML.
+
+ It is added to the callback chain by getDummyData in
+ order to demonstrate how a callback passes its own result
+ to the next callback
+ """
+ return "Result: %s" % r
+
+ def getDummyData (self , x ):
+ """
+ The Deferred mechanism allows for chained callbacks.
+ In this example, the output of gotResults is first
+ passed through _toHTML on its way to printData.
+
+ Again this function is a dummy, simulating a delayed result
+ using callLater, rather than using a real asynchronous
+ setup.
+ """
+ self .d = defer .Deferred ()
+
+
+ reactor .callLater (2 , self .gotResults , x )
+ self .d .addCallback (self ._toHTML )
+ return self .d
+
+def printData (d ):
+ print d
+
+def printError (failure ):
+ import sys
+ sys .stderr .write (str (failure ))
+
+
+g = Getter ()
+d = g .getDummyData (3 )
+d .addCallback (printData )
+d .addErrback (printError )
+
+
+g = Getter ()
+d = g .getDummyData (4 )
+d .addCallback (printData )
+d .addErrback (printError )
+
+reactor .callLater (4 , reactor .stop )
+reactor .run ()
+
+
+
Visual Explanation
+
+
+
+
+
+
+ Requesting method (data sink) requests data, gets
+ Deferred object.
+
+ Requesting method attaches callbacks to Deferred
+ object.
+
+
+
+
+
+ When the result is ready, give it to the Deferred
+ object. .callback(result)
if the operation succeeded,
+ .errback(failure)
if it failed. Note that
+ failure
is typically an instance of a twisted.python.failure.Failure
+ instance.
+
+ Deferred object triggers previously-added (call/err)back
+ with the result
or failure
.
+ Execution then follows the following rules, going down the
+ chain of callbacks to be processed.
+
+
+ Result of the callback is always passed as the first
+ argument to the next callback, creating a chain of
+ processors.
+
+ If a callback raises an exception, switch to
+ errback.
+
+ An unhandled failure gets passed down the line of
+ errbacks, this creating an asynchronous analog to a
+ series to a series of except:
+ statements.
+
+ If an errback doesn't raise an exception or return a
+ twisted.python.failure.Failure
+ instance, switch to callback.
+
+
+
+
+
Errbacks
+
+
Deferred's error handling is modeled after Python's
+exception handling. In the case that no errors occur, all the
+callbacks run, one after the other, as described above.
+
+
If the errback is called instead of the callback (e.g. because a DB query
+raised an error), then a twisted.python.failure.Failure
is passed into the first
+errback (you can add multiple errbacks, just like with callbacks). You can
+think of your errbacks as being like except
blocks
+of ordinary Python code.
+
+
Unless you explicitly raise
an error in except
+block, the Exception
is caught and stops
+propagating, and normal execution continues. The same thing happens with
+errbacks: unless you explicitly return
a Failure
or (re-)raise an exception, the error stops
+propagating, and normal callbacks continue executing from that point (using the
+value returned from the errback). If the errback does returns a Failure
or raise an exception, then that is passed to the
+next errback, and so on.
+
+
Note: If an errback doesn't return anything, then it effectively
+returns None
, meaning that callbacks will continue
+to be executed after this errback. This may not be what you expect to happen,
+so be careful. Make sure your errbacks return a Failure
(probably the one that was passed to it), or a
+meaningful return value for the next callback.
+
+
Also, twisted.python.failure.Failure
instances have
+a useful method called trap, allowing you to effectively do the equivalent
+of:
+
+
1
+2
+3
+4
+5
+6
+
try :
+
+ cookSpamAndEggs ()
+except (SpamException , EggException ):
+
+ ...
+
+
+
You do this by:
+
1
+2
+3
+4
+5
+6
+
def errorHandler (failure ):
+ failure .trap (SpamException , EggException )
+
+
+d .addCallback (cookSpamAndEggs )
+d .addErrback (errorHandler )
+
+
+
If none of arguments passed to failure.trap
+match the error encapsulated in that Failure
, then
+it re-raises the error.
+
+
There's another potential gotcha here. There's a
+method twisted.internet.defer.Deferred.addCallbacks
+which is similar to, but not exactly the same as, addCallback
followed by addErrback
. In particular, consider these two cases:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
+d = getDeferredFromSomewhere ()
+d .addCallback (callback1 )
+d .addErrback (errback1 )
+d .addCallback (callback2 )
+d .addErrback (errback2 )
+
+
+d = getDeferredFromSomewhere ()
+d .addCallbacks (callback1 , errback1 )
+d .addCallbacks (callback2 , errback2 )
+
+
+
If an error occurs in callback1
, then for Case 1
+errback1
will be called with the failure. For Case
+2, errback2
will be called. Be careful with your
+callbacks and errbacks.
+
+
What this means in a practical sense is in Case 1, the callback in line
+A will handle a success condition from getDeferredFromSomewhere
,
+and the errback in line B will handle any errors that occur from either the
+upstream source, or that occur in A . In Case 2, the errback in line C will
+only handle an error condition raised by getDeferredFromSomewhere
,
+it will not do any handling of errors
+raised in callback1
.
+
+
+
Unhandled Errors
+
+
If a Deferred is garbage-collected with an unhandled error (i.e. it would
+call the next errback if there was one), then Twisted will write the error's
+traceback to the log file. This means that you can typically get away with not
+adding errbacks and still get errors logged. Be careful though; if you keep a
+reference to the Deferred around, preventing it from being garbage-collected,
+then you may never see the error (and your callbacks will mysteriously seem to
+have never been called). If unsure, you should explicitly add an errback after
+your callbacks, even if all you do is:
+
+
1
+2
+3
+
+from twisted .python import log
+d .addErrback (log .err )
+
+
+
Handling either synchronous or asynchronous results
+
+In some applications, there are functions that might be either asynchronous or
+synchronous. For example, a user authentication function might be able to
+check in memory whether a user is authenticated, allowing the authentication
+function to return an immediate result, or it may need to wait on
+network data, in which case it should return a Deferred to be fired
+when that data arrives. However, a function that wants to check if a user is
+authenticated will then need to accept both immediate results and
+Deferreds.
+
+
+
+In this example, the library function authenticateUser
uses the
+application function isValidUser
to authenticate a user:
+
+
+
1
+2
+3
+4
+5
+
def authenticateUser (isValidUser , user ):
+ if isValidUser (user ):
+ print "User is authenticated"
+ else :
+ print "User is not authenticated"
+
+
+
+However, it assumes that isValidUser
returns immediately,
+whereas isValidUser
may actually authenticate the user
+asynchronously and return a Deferred. It is possible to adapt this
+trivial user authentication code to accept either a
+synchronous isValidUser
or an
+asynchronous isValidUser
, allowing the library to handle
+either type of function. It is, however, also possible to adapt
+synchronous functions to return Deferreds. This section describes both
+alternatives: handling functions that might be synchronous or
+asynchronous in the library function (authenticateUser
)
+or in the application code.
+
+
+
Handling possible Deferreds in the library code
+
+
+Here is an example of a synchronous user authentication function that might be
+passed to authenticateUser
:
+
+
+
1
+2
+3
+4
+5
+
def synchronousIsValidUser (user ):
+ '''
+ Return true if user is a valid user, false otherwise
+ '''
+ return user in ["Alice" , "Angus" , "Agnes" ]
+
+
+
+However, here's an asynchronousIsValidUser
function that returns
+a Deferred:
+
+
+
1
+2
+3
+4
+5
+6
+
from twisted .internet import reactor , defer
+
+def asynchronousIsValidUser (user ):
+ d = defer .Deferred ()
+ reactor .callLater (2 , d .callback , user in ["Alice" , "Angus" , "Agnes" ])
+ return d
+
+
+
Our original implementation of authenticateUser
expected isValidUser
to be synchronous, but now we need to change it to handle both
+synchronous and asynchronous implementations of isValidUser
. For this, we
+use maybeDeferred
to
+call isValidUser
, ensuring that the result of isValidUser
is a Deferred,
+even if isValidUser
is a synchronous function:
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .internet import defer
+
+def printResult (result ):
+ if result :
+ print "User is authenticated"
+ else :
+ print "User is not authenticated"
+
+def authenticateUser (isValidUser , user ):
+ d = defer .maybeDeferred (isValidUser , user )
+ d .addCallback (printResult )
+
+
+
+Now isValidUser
could be either synchronousIsValidUser
or asynchronousIsValidUser
.
+
+
+
It is also possible to modify synchronousIsValidUser
to return
+a Deferred, see Generating Deferreds for more
+information.
+
+
+
DeferredList
+
+
Sometimes you want to be notified after several different events have all
+happened, rather than waiting for each one individually. For example, you may
+want to wait for all the connections in a list to close. twisted.internet.defer.DeferredList
is the way to do
+this.
+
+
To create a DeferredList from multiple Deferreds, you simply pass a list of
+the Deferreds you want it to wait for:
+
1
+2
+
+dl = defer .DeferredList ([deferred1 , deferred2 , deferred3 ])
+
+
+
You can now treat the DeferredList like an ordinary Deferred; you can call addCallbacks
and so on. The DeferredList will call its callback
+when all the deferreds have completed. The callback will be called with a list
+of the results of the Deferreds it contains, like so:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+def printResult (result ):
+ for (success , value ) in result :
+ if success :
+ print 'Success:' , value
+ else :
+ print 'Failure:' , value .getErrorMessage ()
+
+
+deferred1 = defer .Deferred ()
+deferred2 = defer .Deferred ()
+deferred3 = defer .Deferred ()
+
+
+dl = defer .DeferredList ([deferred1 , deferred2 , deferred3 ], consumeErrors =True )
+
+
+dl .addCallback (printResult )
+
+
+deferred1 .callback ('one' )
+deferred2 .errback (Exception ('bang!' ))
+deferred3 .callback ('three' )
+
+
+
+
+
+
+
+
+
A standard DeferredList will never call errback, but failures in Deferreds
+passed to a DeferredList will still errback unless consumeErrors
+is passed True
. See below for more details about this and other
+flags which modify the behavior of DeferredList.
+
+
Note:
+
If you want to apply callbacks to the individual Deferreds that
+go into the DeferredList, you should be careful about when those callbacks
+are added. The act of adding a Deferred to a DeferredList inserts a callback
+into that Deferred (when that callback is run, it checks to see if the
+DeferredList has been completed yet). The important thing to remember is
+that it is this callback which records the value that goes into the
+result list handed to the DeferredList's callback.
+
+
+
+
Therefore, if you add a callback to the Deferred after adding the
+Deferred to the DeferredList, the value returned by that callback will not
+be given to the DeferredList's callback. To avoid confusion, we recommend not
+adding callbacks to a Deferred once it has been used in a DeferredList.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
def printResult (result ):
+ print result
+def addTen (result ):
+ return result + " ten"
+
+
+deferred1 = defer .Deferred ()
+deferred2 = defer .Deferred ()
+deferred1 .addCallback (addTen )
+dl = defer .DeferredList ([deferred1 , deferred2 ])
+dl .addCallback (printResult )
+deferred1 .callback ("one" )
+deferred2 .callback ("two" )
+
+
+
+
+deferred1 = defer .Deferred ()
+deferred2 = defer .Deferred ()
+dl = defer .DeferredList ([deferred1 , deferred2 ])
+deferred1 .addCallback (addTen )
+dl .addCallback (printResult )
+deferred1 .callback ("one" )
+deferred2 .callback ("two" )
+
+
+
+
+
Other behaviours
+
+
DeferredList accepts three keyword arguments that modify its behaviour:
+fireOnOneCallback
, fireOnOneErrback
and
+consumeErrors
. If fireOnOneCallback
is set, the
+DeferredList will immediately call its callback as soon as any of its Deferreds
+call their callback. Similarly, fireOnOneErrback
will call errback
+as soon as any of the Deferreds call their errback. Note that DeferredList is
+still one-shot, like ordinary Deferreds, so after a callback or errback has been
+called the DeferredList will do nothing further (it will just silently ignore
+any other results from its Deferreds).
+
+
The fireOnOneErrback
option is particularly useful when you
+want to wait for all the results if everything succeeds, but also want to know
+immediately if something fails.
+
+
The consumeErrors
argument will stop the DeferredList from
+propagating any errors along the callback chains of any Deferreds it contains
+(usually creating a DeferredList has no effect on the results passed along the
+callbacks and errbacks of their Deferreds). Stopping errors at the DeferredList
+with this option will prevent Unhandled error in Deferred warnings from
+the Deferreds it contains without needing to add extra errbacks1 . Passing a true value
+for the consumeErrors
parameter will not change the behavior of fireOnOneCallback
or fireOnOneErrback
.
+
+
gatherResults
+
+
A common use for DeferredList is to "join" a number of parallel asynchronous
+operations, finishing successfully if all of the operations were successful, or
+failing if any one of the operations fails. In this case, twisted.internet.defer.gatherResults
is a useful
+shortcut:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .internet import defer
+d1 = defer .Deferred ()
+d2 = defer .Deferred ()
+d = defer .gatherResults ([d1 , d2 ], consumeErrors =True )
+def printResult (result ):
+ print result
+d .addCallback (printResult )
+d1 .callback ("one" )
+
+d2 .callback ("two" )
+
+
+
+
The consumeErrors
argument has the same meaning as it does
+for DeferredList
: if true, it causes
+gatherResults
to consume any errors in the passed-in Deferreds.
+Always use this argument unless you are adding further callbacks or errbacks to
+the passed-in Deferreds, or unless you know that they will not fail.
+Otherwise, a failure will result in an unhandled error being logged by Twisted.
+This argument is available since Twisted 11.1.0.
+
+
+
+
Class Overview
+
+
This is an overview API reference for Deferred from the point of using a
+Deferred returned by a function. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide guidelines
+for its use.
+
+
There is a parallel overview of functions used by the Deferred's creator in Generating Deferreds .
+
+
Basic Callback Functions
+
+
+
+ addCallbacks(self, callback[, errback, callbackArgs,
+ callbackKeywords, errbackArgs, errbackKeywords])
+
+ This is the method you will use to interact
+ with Deferred. It adds a pair of callbacks parallel to
+ each other (see diagram above) in the list of callbacks
+ made when the Deferred is called back to. The signature of
+ a method added using addCallbacks should be
+ myMethod(result, *methodArgs,
+ **methodKeywords)
. If your method is passed in the
+ callback slot, for example, all arguments in the tuple
+ callbackArgs
will be passed as
+ *methodArgs
to your method.
+
+ There are various convenience methods that are
+ derivative of addCallbacks. I will not cover them in detail
+ here, but it is important to know about them in order to
+ create concise code.
+
+
+
+ addCallback(callback, *callbackArgs,
+ **callbackKeywords)
+
+ Adds your callback at the next point in the
+ processing chain, while adding an errback that will
+ re-raise its first argument, not affecting further
+ processing in the error case.
+
+ Note that, while addCallbacks (plural) requires the arguments to be
+ passed in a tuple, addCallback (singular) takes all its remaining
+ arguments as things to be passed to the callback function. The reason is
+ obvious: addCallbacks (plural) cannot tell whether the arguments are
+ meant for the callback or the errback, so they must be specifically
+ marked by putting them into a tuple. addCallback (singular) knows that
+ everything is destined to go to the callback, so it can use Python's
+ * and ** syntax to collect the remaining arguments.
+
+
+
+
+ addErrback(errback, *errbackArgs,
+ **errbackKeywords)
+
+ Adds your errback at the next point in the
+ processing chain, while adding a callback that will
+ return its first argument, not affecting further
+ processing in the success case.
+
+
+
+ addBoth(callbackOrErrback,
+ *callbackOrErrbackArgs,
+ **callbackOrErrbackKeywords)
+
+ This method adds the same callback into both sides
+ of the processing chain at both points. Keep in mind
+ that the type of the first argument is indeterminate if
+ you use this method! Use it for finally:
+ style blocks.
+
+
+
+
+
+
+
Chaining Deferreds
+
+
If you need one Deferred to wait on another, all you need to do is return a
+Deferred from a method added to addCallbacks. Specifically, if you return
+Deferred B from a method added to Deferred A using A.addCallbacks, Deferred A's
+processing chain will stop until Deferred B's .callback() method is called; at
+that point, the next callback in A will be passed the result of the last
+callback in Deferred B's processing chain at the time.
+
+
If this seems confusing, don't worry about it right now -- when you run into
+a situation where you need this behavior, you will probably recognize it
+immediately and realize why this happens. If you want to chain deferreds
+manually, there is also a convenience method to help you.
+
+
+
+ chainDeferred(otherDeferred)
+
+ Add otherDeferred
to the end of this
+ Deferred's processing chain. When self.callback is called,
+ the result of my processing chain up to this point will be
+ passed to otherDeferred.callback
. Further
+ additions to my callback chain do not affect
+ otherDeferred
+ This is the same as self.addCallbacks(otherDeferred.callback,
+ otherDeferred.errback)
+
+
+
+
See also
+
+
+Generating Deferreds , an introduction to
+writing asynchronous functions that return Deferreds.
+
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/design.html b/doc/core/howto/design.html
new file mode 100644
index 0000000..49e292c
--- /dev/null
+++ b/doc/core/howto/design.html
@@ -0,0 +1,254 @@
+
+
+Twisted Documentation: Designing Twisted Applications
+
+
+
+
+ Designing Twisted Applications
+
+
+
+
+
+
Goals
+
+
This document describes how a good Twisted application is structured. It
+should be useful for beginning Twisted developers who want to structure their
+code in a clean, maintainable way that reflects current best practices.
+
+
Readers will want to be familiar with writing servers and clients using Twisted.
+
+
Example of a modular design: TwistedQuotes
+
+
TwistedQuotes
is a very simple plugin which is a great
+demonstration of
+Twisted's power. It will export a small kernel of functionality -- Quote of
+the Day -- which can be accessed through every interface that Twisted supports:
+web pages, e-mail, instant messaging, a specific Quote of the Day protocol, and
+more.
+
+
Set up the project directory
+
+
See the description of setting up the TwistedQuotes
+example .
+
+
A Look at the Heart of the Application
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
from random import choice
+
+from zope .interface import implements
+
+from TwistedQuotes import quoteproto
+
+
+
+class StaticQuoter :
+ """
+ Return a static quote.
+ """
+
+ implements (quoteproto .IQuoter )
+
+ def __init__ (self , quote ):
+ self .quote = quote
+
+
+ def getQuote (self ):
+ return self .quote
+
+
+
+class FortuneQuoter :
+ """
+ Load quotes from a fortune-format file.
+ """
+ implements (quoteproto .IQuoter )
+
+ def __init__ (self , filenames ):
+ self .filenames = filenames
+
+
+ def getQuote (self ):
+ quoteFile = file (choice (self .filenames ))
+ quotes = quoteFile .read ().split ('\n%\n' )
+ quoteFile .close ()
+ return choice (quotes )
+
+
+
This code listing shows us what the Twisted Quotes system is all about. The
+code doesn't have any way of talking to the outside world, but it provides a
+library which is a clear and uncluttered abstraction: give me the quote of
+the day .
+
+
Note that this module does not import any Twisted functionality at all! The
+reason for doing things this way is integration. If your business
+objects are not stuck to your user interface, you can make a module that
+can integrate those objects with different protocols, GUIs, and file formats.
+Having such classes provides a way to decouple your components from each other,
+by allowing each to be used independently.
+
+
In this manner, Twisted itself has minimal impact on the logic of your
+program. Although the Twisted dot products are highly interoperable,
+they
+also follow this approach. You can use them independently because they are not
+stuck to each other. They communicate in well-defined ways, and only when that
+communication provides some additional feature. Thus, you can use twisted.web
with twisted.enterprise
, but neither requires the other, because
+they are integrated around the concept of Deferreds .
+
+
Your Twisted applications should follow this style as much as possible.
+Have (at least) one module which implements your specific functionality,
+independent of any user-interface code.
+
+
Next, we're going to need to associate this abstract logic with some way of
+displaying it to the user. We'll do this by writing a Twisted server protocol,
+which will respond to the clients that connect to it by sending a quote to the
+client and then closing the connection. Note: don't get too focused on the
+details of this -- different ways to interface with the user are 90% of what
+Twisted does, and there are lots of documents describing the different ways to
+do it.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
from zope .interface import Interface
+
+from twisted .internet .protocol import Factory , Protocol
+
+
+
+class IQuoter (Interface ):
+ """
+ An object that returns quotes.
+ """
+ def getQuote ():
+ """
+ Return a quote.
+ """
+
+
+
+class QOTD (Protocol ):
+ def connectionMade (self ):
+ self .transport .write (self .factory .quoter .getQuote ()+'\r\n' )
+ self .transport .loseConnection ()
+
+
+
+class QOTDFactory (Factory ):
+ """
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ """
+ protocol = QOTD
+
+ def __init__ (self , quoter ):
+ self .quoter = quoter
+
+
+
This is a very straightforward Protocol
implementation, and the
+pattern described above is repeated here. The Protocol contains essentially no
+logic of its own, just enough to tie together an object which can generate
+quotes (a Quoter
) and an object which can relay
+bytes to a TCP connection (a Transport
). When a
+client connects to this server, a QOTD
instance is
+created, and its connectionMade
method is called.
+
+
+
The QOTDFactory
's role is to specify to the
+Twisted framework how to create a Protocol
instance
+that will handle the connection. Twisted will not instantiate a QOTDFactory
; you will do that yourself later, in a twistd
plug-in.
+
+
+
Note: you can read more specifics of Protocol
and Factory
in the Writing
+Servers HOWTO.
+
+
Once we have an abstraction -- a Quoter
-- and we have a
+mechanism to connect it to the network -- the QOTD
protocol -- the
+next thing to do is to put the last link in the chain of functionality between
+abstraction and user. This last link will allow a user to choose a Quoter
and configure the protocol. Writing this configuration is
+covered in the Application HOWTO .
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/dirdbm.html b/doc/core/howto/dirdbm.html
new file mode 100644
index 0000000..f2b86b2
--- /dev/null
+++ b/doc/core/howto/dirdbm.html
@@ -0,0 +1,76 @@
+
+
+Twisted Documentation: DirDBM: Directory-based Storage
+
+
+
+
+ DirDBM: Directory-based Storage
+
+
+
+
+
dirdbm.DirDBM
+
+
twisted.persisted.dirdbm.DirDBM
is a DBM-like storage system.
+That is, it stores mappings between keys
+and values, like a Python dictionary, except that it stores the values in files
+in a directory - each entry is a different file. The keys must always be strings,
+as are the values. Other than that, DirDBM
+objects act just like Python dictionaries.
+
+
DirDBM
is useful for cases
+when you want to store small amounts of data in an organized fashion, without having
+to deal with the complexity of a RDBMS or other sophisticated database. It is simple,
+easy to use, cross-platform, and doesn't require any external C libraries, unlike
+Python's built-in DBM modules.
+
+
+>>> from twisted.persisted import dirdbm
+>>> d = dirdbm.DirDBM("/tmp/dir")
+>>> d["librarian"] = "ook"
+>>> d["librarian"]
+'ook'
+>>> d.keys()
+['librarian']
+>>> del d["librarian"]
+>>> d.items()
+[]
+
+
+
dirdbm.Shelf
+
+
Sometimes it is neccessary to persist more complicated objects than strings.
+With some care, dirdbm.Shelf
+can transparently persist
+them. Shelf
works exactly like DirDBM
, except that
+the values (but not the keys) can be arbitrary picklable objects. However,
+notice that mutating an object after it has been stored in the Shelf
has no effect on the Shelf.
+When mutating objects, it is neccessary to explictly store them back in the Shelf
+afterwards:
+
+
+>>> from twisted.persisted import dirdbm
+>>> d = dirdbm.Shelf("/tmp/dir2")
+>>> d["key"] = [1, 2]
+>>> d["key"]
+[1, 2]
+>>> l = d["key"]
+>>> l.append(3)
+>>> d["key"]
+[1, 2]
+>>> d["key"] = l
+>>> d["key"]
+[1, 2, 3]
+
+
+
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/endpoints.html b/doc/core/howto/endpoints.html
new file mode 100644
index 0000000..333a3fd
--- /dev/null
+++ b/doc/core/howto/endpoints.html
@@ -0,0 +1,231 @@
+
+
+Twisted Documentation: Getting Connected with Endpoints
+
+
+
+
+ Getting Connected with Endpoints
+
+
+
+
+
Introduction
+
+
On a network, one can think of any given connection as a long wire,
+stretched between two points. Lots of stuff can happen along the length of
+that wire - routers, switches, network address translation, and so on, but
+that is usually invisible to the application passing data across it.
+Twisted strives to make the nature of the "wire" as transparent as
+possible, with highly abstract interfaces for passing and receiving data,
+such as ITransport
+and IProtocol
.
+
+
However, the application can't be completely ignorant of the wire.
+In particular, it must do something to start the connection, and
+to do so, it must identify the end points of the wire. There are
+different names for the roles of each end point - "initiator" and
+"responder", "connector" and "listener", or "client" and "server" - but the
+common theme is that one side of the connection waits around for someone to
+connect to it, and the other side does the connecting.
+
+
In Twisted 10.1, several new interfaces were introduced to describe
+each of these roles for stream-oriented connections: IStreamServerEndpoint
and IStreamClientEndpoint
.
+The word "stream", in this case, refers to endpoints which treat a
+connection as a continuous stream of bytes, rather than a sequence of
+discrete datagrams: TCP is a "stream" protocol whereas UDP is a "datagram"
+protocol.
+
+
Constructing and Using Endpoints
+
+
In both Writing Servers and Writing Clients , we covered basic usage of
+endpoints; you construct an appropriate type of server or client endpoint,
+and then call listen
(for servers) or connect
+(for clients).
+
+
In both of those tutorials, we constructed specific types of
+endpoints directly. However, in most programs, you will want to allow the
+user to specify where to listen or connect, in a way which will allow the
+user to request different strategies, without having to adjust your
+program. In order to allow this, you should use clientFromString
or serverFromString
.
+
+
There's Not Much To It
+
+
Each type of endpoint is just an interface with a single method that
+takes an argument. serverEndpoint.listen(factory)
will start
+listening on that endpoint with your protocol factory, and
+clientEndpoint.connect(factory)
will start a single connection
+attempt. Each of these APIs returns a value, though, which can be important.
+
+
+
However, if you are not already, you should be very
+familiar with Deferreds , as they are returned by
+both connect
and listen
methods, to indicate when
+the connection has connected or the listening port is up and running.
+
+
Servers and Stopping
+
+
IStreamServerEndpoint.listen
+returns a Deferred
+that fires with an IListeningPort
.
+Note that this deferred may errback. The most common cause of such an error
+would be that another program is already using the requested port number,
+but the exact cause may vary depending on what type of endpoint you are
+listening on. If you receive such an error, it means that your application
+is not actually listening, and will not receive any incoming connections.
+It's important to somehow alert an administrator of your server, in this
+case, especially if you only have one listening port!
+
+
Note also that once this has succeeded, it will continue listening
+forever. If you need to stop listening for some reason, in
+response to anything other than a full server shutdown (reactor.stop
+and / or twistd
will usually handle that case for you), make
+sure you keep a reference around to that listening port object so you can
+call IListeningPort.stopListening
+on it. Finally, keep in mind that stopListening
itself returns
+a Deferred
, and the port may not have fully stopped listening
+until that Deferred
has fired.
+
+
Most server applications will not need to worry about these details.
+One example of a case where you would need to be concerned with all of
+these events would be an implementation of a protocol like non-PASV
+FTP, where new listening ports need to be bound for the lifetime of a
+particular action, then disposed of.
+
+
Clients and Cancelling
+
+
IStreamClientEndpoint.connect
+will connect your protocol factory to a new outgoing connection attempt. It
+returns a Deferred
which fires with the IProtocol
+returned from the factory's buildProtocol
method.
+
+
Connection attempts may fail, and so that Deferred
may also errback. If it does so,
+you will have to try again; your protocol won't be constructed, and no further
+attempts will be made.
+
+
Connection attempts may also take a long time, and your users may
+become bored and wander off. If this happens, and your code decides, for
+whatever reason, that you've been waiting for the connection too long, you
+can call Deferred.cancel
+on the Deferred
returned from connect
, and the
+underlying machinery should give up on the connection. This should cause the
+Deferred
to errback, usually with CancelledError
; although you should
+consult the documentation for your particular endpoint type to see if it may do
+something different.
+
+
Although some endpoint types may imply a built-in timeout, the
+interface does not guarantee one. If you don't have any way for the
+application to cancel a wayward connection attempt, the attempt may just
+keep waiting forever. For example, a very simple 30-second timeout could be
+implemented like this:
+
1
+2
+
attempt = myEndpoint .connect (myFactory )
+reactor .callLater (30 , attempt .cancel )
+
+
+
+
Maximizing the Return on your Endpoint Investment
+
+
Directly constructing an endpoint in your application is rarely the
+best option, because it ties your application to a particular type of
+transport. The strength of the endpoints API is in separating the
+construction of the endpoint (figuring out where to connect or listen) and
+its activation (actually connecting or listening).
+
+
If you are implementing a library that needs to listen for
+connections or make outgoing connections, when possible, you should write
+your code to accept client and server endpoints as parameters to functions
+or to your objects' constructors. That way, application code that calls
+your library can provide whatever endpoints are appropriate.
+
+
If you are writing an application and you need to construct
+endpoints yourself, you can allow users to specify arbitrary endpoints
+described by a string using the clientFromString
and serverFromString
+APIs. Since these APIs just take a string, they provide flexibility: if
+Twisted adds support for new types of endpoints (for example, IPv6
+endpoints, or WebSocket endpoints), your application will automatically be
+able to take advantage of them with no changes to its code.
+
+
Endpoints Aren't Always the Answer
+
+
For many use-cases, especially the common case of a twistd
+plugin which runs a long-running server that just binds a simple port, you
+might not want to use the endpoints APIs directly. Instead, you may want to
+construct an IService
, using strports.service
, which will fit
+neatly into the required structure of the twistd
+plugin API . This doesn't give your application much control - the port
+starts listening at startup and stops listening at shutdown - but it does
+provide the same flexibility in terms of what type of server endpoint your
+application will support.
+
+
It is, however, almost always preferable to use an endpoint rather
+than calling a lower-level APIs like connectTCP
, listenTCP
,
+etc, directly. By accepting an arbitrary endpoint rather than requiring a
+specific reactor interface, you leave your application open to lots of
+interesting transport-layer extensibility for the future.
+
+
Endpoint Types Included With Twisted
+
+
The parser used by clientFromString
and
+serverFromString
is extensible via third-party plugins, so the
+endpoints available on your system depend on what packages you have installed.
+However, Twisted itself includes a set of basic endpoints that will always be
+available.
+
+
Clients
+
+
+ TCP. Supported arguments: host, port, timeout. timeout is optional. For
+ example, tcp:host=twistedmatrix.com:port=80:timeout=15
.
+
+ SSL. All TCP arguments are supported, plus: certKey, privateKey,
+ caCertsDir. certKey (optional) gives a filesystem path to a certificate (PEM
+ format). privateKey (optional) gives a filesystem path to a a private key
+ (PEM format). caCertsDir (optional) gives a filesystem path to a directory
+ containing trusted CA certificates to use to verify the server certificate.
+ For example,
+ ssl:host=twistedmatrix.com:port=443:caCertsDir=/etc/ssl/certs
.
+
+ UNIX. Supported arguments: path, timeout, checkPID. path gives a
+ filesystem path to a listening UNIX domain socket server. checkPID (optional)
+ enables a check of the lock file Twisted-based UNIX domain socket servers use
+ to prove they are still running. For
+ example, unix:path=/var/run/web.sock
.
+
+
+
+
Servers
+
+
+ TCP. Supported arguments: port, interface, backlog. interface and
+ backlog are optional. interface is an IP address to bind to. For example,
+ tcp:port=80:interface=192.168.1.1
.
+
+ SSL. All TCP arguments are supported, plus: certKey, privateKey, and
+ sslmethod. certKey (optional, defaults to the value of privateKey) gives a
+ filesystem path to a certificate (PEM format). privateKey gives a filesystem
+ path to a a private key (PEM format). sslmethod indicates which SSL/TLS
+ version to use (a value like TLSv1_METHOD). For example,
+ ssl:port=443:privateKey=/etc/ssl/server.pem:sslmethod=SSLv3_METHOD
.
+
+ UNIX. Supported arguments: address, mode, backlog, lockfile. address
+ gives a filesystem path to listen on with a UNIX domain socket server. mode
+ (optional) gives the filesystem permission/mode (in octal) to apply to that
+ socket. lockfile enables use of a separate lock file to prove the server is
+ still running. For example, unix:address=/var/run/web.sock:lockfile=1
.
+
+ systemd. Supported arguments: domain, index. domain indicates which
+ socket domain the inherited file descriptor belongs to (eg INET, INET6).
+ index indicates an offset into the array of file descriptors which have been
+ inherited from systemd. For
+ example, systemd:domain=INET6:index=3
.
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/gendefer.html b/doc/core/howto/gendefer.html
new file mode 100644
index 0000000..44b35b9
--- /dev/null
+++ b/doc/core/howto/gendefer.html
@@ -0,0 +1,411 @@
+
+
+Twisted Documentation: Generating Deferreds
+
+
+
+
+ Generating Deferreds
+
+
+
+
+
+
Deferred
objects are
+signals that a function you have called does not yet have the data you want
+available. When a function returns a Deferred object, your calling function
+attaches callbacks to it to handle the data when available.
+
+
This document addresses the other half of the question: writing functions
+that return Deferreds, that is, constructing Deferred objects, arranging for
+them to be returned immediately without blocking until data is available, and
+firing their callbacks when the data is available.
+
+
This document assumes that you are familiar with the asynchronous model used
+by Twisted, and with using deferreds returned by functions
+.
+
+
+
+
Class overview
+
+
This is an overview API reference for Deferred from the point of creating a
+Deferred and firing its callbacks and errbacks. It is not meant to be a
+substitute for the docstrings in the Deferred class, but can provide
+guidelines for its use.
+
+
There is a parallel overview of functions used by calling function which
+the Deferred is returned to at Using Deferreds .
+
+
Basic Callback Functions
+
+
+
+ callback(result)
+
+ Run success callbacks with the given result. This
+ can only be run once. Later calls to this or
+ errback
will raise twisted.internet.defer.AlreadyCalledError
.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.
+
+
+
+ errback(failure)
+
+ Run error callbacks with the given failure. This can
+ only be run once. Later calls to this or
+ callback
will raise twisted.internet.defer.AlreadyCalledError
.
+ If further callbacks or errbacks are added after this
+ point, addCallbacks will run the callbacks immediately.
+
+
+
+
What Deferreds don't do: make your code asynchronous
+
+
Deferreds do not make the code magically not block.
+
+
Let's take this function as an example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
from twisted .internet import defer
+
+TARGET = 10000
+
+def largeFibonnaciNumber ():
+
+ d = defer .Deferred ()
+
+
+
+ first = 0
+ second = 1
+
+ for i in xrange (TARGET - 1 ):
+ new = first + second
+ first = second
+ second = new
+ if i % 100 == 0 :
+ print "Progress: calculating the %dth Fibonnaci number" % i
+
+
+ d .callback (second )
+
+
+ return d
+
+import time
+
+timeBefore = time .time ()
+
+
+d = largeFibonnaciNumber ()
+
+timeAfter = time .time ()
+
+print "Total time taken for largeFibonnaciNumber call: %0.3f seconds" %
+ (timeAfter - timeBefore )
+
+
+
+def printNumber (number ):
+ print "The %dth Fibonacci number is %d" % (TARGET , number )
+
+print "Adding the callback now."
+
+d .addCallback (printNumber )
+
+
+
You will notice that despite creating a Deferred in the largeFibonnaciNumber
function, these things happened:
+
+the "Total time taken for largeFibonnaciNumber call" output
+shows that the function did not return immediately as asynchronous functions
+are expected to do; and
+rather than the callback being added before the result was available and
+called after the result is available, it isn't even added until after the
+calculation has been completed.
+
+
+
The function completed its calculation before returning, blocking the
+process until it had finished, which is exactly what asynchronous functions
+are not meant to do. Deferreds are not a non-blocking talisman: they are a
+signal for asynchronous functions to use to pass results onto
+callbacks, but using them does not guarantee that you have an asynchronous
+function.
+
+
+
Advanced Processing Chain Control
+
+
+
+ pause()
+
+ Cease calling any methods as they are added, and do not
+ respond to callback
, until
+ self.unpause()
is called.
+
+
+
+ unpause()
+
+ If callback
has been called on this
+ Deferred already, call all the callbacks that have been
+ added to this Deferred since pause
was
+ called.
+
+ Whether it was called or not, this will put this
+ Deferred in a state where further calls to
+ addCallbacks
or callback
will
+ work as normal.
+
+
+
+
Returning Deferreds from synchronous functions
+
+
Sometimes you might wish to return a Deferred from a synchronous function.
+There are several reasons why, the major two are maintaining API compatibility
+with another version of your function which returns a Deferred, or allowing
+for the possiblity that in the future your function might need to be
+asynchronous.
+
+
In the Using Deferreds reference, we gave the
+following example of a synchronous function:
+
+
1
+2
+3
+4
+5
+
def synchronousIsValidUser (user ):
+ '''
+ Return true if user is a valid user, false otherwise
+ '''
+ return user in ["Alice" , "Angus" , "Agnes" ]
+
+
+
While we can require that callers of our function wrap our synchronous
+result in a Deferred using maybeDeferred
, for the sake of API
+compatibility it is better to return a Deferred ourselves using defer.succeed
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
from twisted .internet import defer
+
+def immediateIsValidUser (user ):
+ '''
+ Returns a Deferred resulting in true if user is a valid user, false
+ otherwise
+ '''
+
+ result = user in ["Alice" , "Angus" , "Agnes" ]
+
+
+ return defer .succeed (result )
+
+
+
There is an equivalent defer.fail
method to return a Deferred with the
+errback chain already fired.
+
+
Integrating blocking code with Twisted
+
+
At some point, you are likely to need to call a blocking function: many
+functions in third party libraries will have long running blocking functions.
+There is no way to 'force' a function to be asynchronous: it must be written
+that way specifically. When using Twisted, your own code should be
+asynchronous, but there is no way to make third party functions asynchronous
+other than rewriting them.
+
+
In this case, Twisted provides the ability to run the blocking code in a
+separate thread rather than letting it block your application. The twisted.internet.threads.deferToThread
function will set up
+a thread to run your blocking function, return a Deferred and later fire that
+Deferred when the thread completes.
+
+
Let's assume our largeFibonnaciNumber
function
+from above is in a third party library (returning the result of the
+calculation, not a Deferred) and is not easily modifiable to be finished in
+discrete blocks. This example shows it being called in a thread, unlike in the
+earlier section we'll see that the operation does not block our entire
+program:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
def largeFibonnaciNumber ():
+ """
+ Represent a long running blocking function by calculating
+ the TARGETth Fibonnaci number
+ """
+ TARGET = 10000
+
+ first = 0
+ second = 1
+
+ for i in xrange (TARGET - 1 ):
+ new = first + second
+ first = second
+ second = new
+
+ return second
+
+from twisted .internet import threads , reactor
+
+def fibonacciCallback (result ):
+ """
+ Callback which manages the largeFibonnaciNumber result by
+ printing it out
+ """
+ print "largeFibonnaciNumber result =" , result
+
+
+ reactor .stop ()
+
+def run ():
+ """
+ Run a series of operations, deferring the largeFibonnaciNumber
+ operation to a thread and performing some other operations after
+ adding the callback
+ """
+
+ d = threads .deferToThread (largeFibonnaciNumber )
+
+ d .addCallback (fibonacciCallback )
+ print "1st line after the addition of the callback"
+ print "2nd line after the addition of the callback"
+
+if __name__ == '__main__' :
+ run ()
+ reactor .run ()
+
+
+
Possible sources of error
+
+
Deferreds greatly simplify the process of writing asynchronous code by
+providing a standard for registering callbacks, but there are some subtle and
+sometimes confusing rules that you need to follow if you are going to use
+them. This mostly applies to people who are writing new systems that use
+Deferreds internally, and not writers of applications that just add callbacks
+to Deferreds produced and processed by other systems. Nevertheless, it is good
+to know.
+
+
Firing Deferreds more than once is impossible
+
+
Deferreds are one-shot. You can only call Deferred.callback
or Deferred.errback
once. The processing chain continues each time
+you add new callbacks to an already-called-back-to Deferred.
+
+
Synchronous callback execution
+
+
If a Deferred already has a result available, addCallback
may call the callback synchronously: that is, immediately
+after it's been added. In situations where callbacks modify state, it is
+might be desirable for the chain of processing to halt until all callbacks are
+added. For this, it is possible to pause
and unpause
+a Deferred's processing chain while you are adding lots of callbacks.
+
+
Be careful when you use these methods! If you pause
a
+Deferred, it is your responsibility to make sure that you unpause it.
+The function adding the callbacks must unpause a paused Deferred, it should never be the responsibility of the code that actually fires the
+callback chain by calling callback
or errback
as
+this would negate its usefulness!
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/glossary.html b/doc/core/howto/glossary.html
new file mode 100644
index 0000000..c6d8ea9
--- /dev/null
+++ b/doc/core/howto/glossary.html
@@ -0,0 +1,334 @@
+
+
+Twisted Documentation: Twisted Glossary
+
+
+
+
+ Twisted Glossary
+
+
+
+
+
+
+adaptee
+
+ An object that has been adapted, also called original . See Adapter .
+
+
+Adapter
+
+ An object whose sole purpose is to implement an Interface for another object.
+ See Interfaces and Adapters .
+
+
+Application
+
+ A twisted.application.service.Application
. There are
+ HOWTOs on creating and manipulating them as a
+ system-administrator, as well as using them in
+ your code.
+
+
+Avatar
+
+ (from Twisted Cred ) business logic for specific user.
+ For example, in PB these are perspectives, in POP3 these
+ are mailboxes, and so on.
+
+
+Banana
+
+ The low-level data marshalling layer of Twisted Spread .
+ See twisted.spread.banana
.
+
+
+Broker
+
+ A twisted.spread.pb.Broker
, the object request
+ broker for Twisted Spread .
+
+
+cache
+
+ A way to store data in readily accessible place for later reuse. Caching data
+ is often done because the data is expensive to produce or access. Caching data
+ risks being stale, or out of sync with the original data.
+
+
+component
+
+ A special kind of (persistent) Adapter
that works with a twisted.python.components.Componentized
. See also Interfaces and Adapters .
+
+
+Componentized
+
+ A Componentized object is a collection of information, separated
+ into domain-specific or role-specific instances, that all stick
+ together and refer to each other.
+ Each object is an Adapter
, which, in the
+ context of Componentized, we call components . See also Interfaces and Adapters .
+
+
+conch
+Twisted's SSH implementation.
+
+Connector
+
+ Object used to interface between client connections and protocols, usually
+ used with a twisted.internet.protocol.ClientFactory
+ to give you control over how a client connection reconnects. See twisted.internet.interfaces.IConnector
and Writing Clients .
+
+
+Consumer
+
+ An object that consumes data from a Producer . See
+ twisted.internet.interfaces.IConsumer
.
+
+
+Cred
+
+ Twisted's authentication API, twisted.cred
. See
+ Introduction to Twisted Cred and
+ Twisted Cred usage .
+
+
+credentials
+
+ A username/password, public key, or some other information used for
+ authentication.
+
+
+credential checker
+
+ Where authentication actually happens. See
+ ICredentialsChecker
.
+
+
+CVSToys
+A nifty set of tools for CVS, available at
+http://twistedmatrix.com/users/acapnotic/wares/code/CVSToys/ .
+
+Daemon
+
+ A background process that does a job or handles client requests.
+ Daemon is a Unix term; service is the Windows equivalent.
+
+
+Deferred
+
+ A instance of twisted.internet.defer.Deferred
, an
+ abstraction for handling chains of callbacks and error handlers
+ (errbacks ).
+ See the Deferring Execution HOWTO.
+
+
+Enterprise
+
+ Twisted's RDBMS support. It contains twisted.enterprise.adbapi
for asynchronous access to any
+ standard DB-API 2.0 module. See Introduction to
+ Twisted Enterprise for more details.
+
+
+errback
+
+ A callback attached to a Deferred with
+ .addErrback
to handle errors.
+
+
+Factory
+
+ In general, an object that constructs other objects. In Twisted, a Factory
+ usually refers to a twisted.internet.protocol.Factory
, which constructs
+ Protocol instances for incoming or outgoing
+ connections. See Writing Servers and Writing Clients .
+
+
+Failure
+
+ Basically, an asynchronous exception that contains traceback information;
+ these are used for passing errors through asynchronous callbacks.
+
+
+im
+
+ Abbreviation of (Twisted) Instance
+ Messenger .
+
+
+Instance Messenger
+
+ Instance Messenger is a multi-protocol chat program that comes with
+ Twisted. It can communicate via TOC with the AOL servers, via IRC, as well as
+ via PB with
+ Twisted Words . See twisted.words.im
.
+
+
+Interface
+
+ A class that defines and documents methods that a class conforming to that
+ interface needs to have. A collection of core twisted.internet
interfaces can
+ be found in twisted.internet.interfaces
. See also Interfaces and Adapters .
+
+
+Jelly
+
+ The serialization layer for Twisted Spread , although it
+ can be used seperately from Twisted Spread as well. It is similar in purpose
+ to Python's standard pickle
module, but is more
+ network-friendly, and depends on a separate marshaller (Banana , in most cases). See twisted.spread.jelly
.
+
+
+Lore
+
+Lore is
+Twisted's documentation system. The source format is a subset of
+XHTML, and output formats include HTML and LaTeX.
+
+Manhole
+
+ A debugging/administration interface to a Twisted application.
+
+
+Microdom
+
+ A partial DOM implementation using SUX . It is simple and
+ pythonic, rather than strictly standards-compliant. See twisted.web.microdom
.
+
+
+Names
+Twisted's DNS server, found in twisted.names
.
+
+Nevow
+The successor to Woven ; available from Divmod .
+
+
+PB
+
+ Abbreviation of Perspective
+ Broker .
+
+
+Perspective Broker
+
+ The high-level object layer of Twisted Spread ,
+ implementing semantics for method calling and object copying, caching, and
+ referencing. See twisted.spread.pb
.
+
+
+Portal
+
+ Glues credential checkers and
+ realm s together.
+
+
+Producer
+
+ An object that generates data a chunk at a time, usually to be processed by a
+ Consumer . See
+ twisted.internet.interfaces.IProducer
.
+
+
+Protocol
+
+ In general each network connection has its own Protocol instance to manage
+ connection-specific state. There is a collection of standard
+ protocol implementations in twisted.protocols
. See
+ also Writing Servers and Writing Clients .
+
+
+PSU
+There is no PSU.
+
+Reactor
+
+ The core event-loop of a Twisted application. See
+ Reactor Basics .
+
+
+Reality
+See Twisted Reality
+
+realm
+
+ (in Twisted Cred ) stores avatars
+ and perhaps general business logic. See
+ IRealm
.
+
+
+Resource
+
+ A twisted.web.resource.Resource
, which are served
+ by Twisted Web. Resources can be as simple as a static file on disk, or they
+ can have dynamically generated content.
+
+
+Service
+
+ A twisted.application.service.Service
. See Application howto for a description of how they
+ relate to Applications .
+
+
+Spread
+Twisted Spread is
+Twisted's remote-object suite. It consists of three layers:
+Perspective Broker , Jelly
+and Banana. See Writing Applications
+with Perspective Broker .
+
+SUX
+S mall U ncomplicated X ML, Twisted's simple XML
+parser written in pure Python. See twisted.web.sux
.
+
+TAC
+A T wisted A pplication C onfiguration is a Python
+source file, generally with the .tac extension, which defines
+configuration to make an application runnable using twistd
.
+
+TAP
+T wisted A pplication P ickle (no longer supported), or simply just a
+T wisted AP plication. A serialised application that was created
+with mktap
(no longer supported) and runnable by twistd
. See
+Using the Utilities .
+
+Trial
+twisted.trial
, Twisted's unit-testing framework,
+based on the unittest
standard library module. See also Writing tests for Twisted code .
+
+Twisted Matrix Laboratories
+The team behind Twisted.
+http://twistedmatrix.com/ .
+
+Twisted Reality
+
+In days of old, the Twisted Reality multiplayer text-based interactive-fiction
+system was the main focus of Twisted Matrix Labs; Twisted, the general networking
+framework, grew out of Reality's need for better network functionality. Twisted
+Reality has been superseded by the Imaginary project.
+
+
+usage
+The twisted.python.usage
module, a replacement for
+the standard getopt
module for parsing command-lines which is much
+easier to work with. See Parsing command-lines .
+
+Words
+Twisted Words is a multi-protocol chat server that uses the
+Perspective Broker protocol as its native
+communication style. See twisted.words
.
+
+Woven
+W eb O bject V isualization En vironment.
+A templating system previously, but no longer, included with Twisted. Woven
+has largely been superceded by
+Divmod Nevow .
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/howto.tidyrc b/doc/core/howto/howto.tidyrc
new file mode 100644
index 0000000..6896505
--- /dev/null
+++ b/doc/core/howto/howto.tidyrc
@@ -0,0 +1,6 @@
+output-xml: yes
+output-xhtml: yes
+tidy-mark: no
+indent: auto
+gnu-emacs: yes
+add-xml-decl: yes
\ No newline at end of file
diff --git a/doc/core/howto/index.html b/doc/core/howto/index.html
new file mode 100644
index 0000000..76cb5bb
--- /dev/null
+++ b/doc/core/howto/index.html
@@ -0,0 +1,239 @@
+
+
+Twisted Documentation: Twisted Documentation
+
+
+
+
+ Twisted Documentation
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/internet-overview.html b/doc/core/howto/internet-overview.html
new file mode 100644
index 0000000..2da29dd
--- /dev/null
+++ b/doc/core/howto/internet-overview.html
@@ -0,0 +1,48 @@
+
+
+Twisted Documentation: Overview of Twisted Internet
+
+
+
+
+ Overview of Twisted Internet
+
+
+
+
+
+
Twisted Internet is a collection of compatible event-loops for Python.
+It contains the code to dispatch events to interested observers and a portable
+API so that observers need not care about which event loop is running. Thus,
+it is possible to use the same code for different loops, from Twisted's basic,
+yet portable, select
-based loop to the loops of various GUI
+toolkits like GTK+ or Tk.
+
+
Twisted Internet contains the various interfaces to the reactor
+API, whose usage is documented in the low-level chapter. Those APIs
+are IReactorCore
,
+ IReactorTCP
,
+ IReactorSSL
,
+ IReactorUNIX
,
+ IReactorUDP
,
+ IReactorTime
,
+ IReactorProcess
,
+ IReactorMulticast
+and IReactorThreads
.
+The reactor APIs allow non-persistent calls to be made.
+
+
Twisted Internet also covers the interfaces for the various transports,
+in ITransport
+and friends. These interfaces allow Twisted network code to be written without
+regard to the underlying implementation of the transport.
+
+
The IProtocolFactory
+dictates how factories, which are usually a large part of third party code, are
+written.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/listings/TwistedQuotes/__init__.py b/doc/core/howto/listings/TwistedQuotes/__init__.py
new file mode 100644
index 0000000..ed6bd97
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/__init__.py
@@ -0,0 +1,3 @@
+"""
+Twisted Quotes
+"""
diff --git a/doc/core/howto/listings/TwistedQuotes/pbquote.py b/doc/core/howto/listings/TwistedQuotes/pbquote.py
new file mode 100644
index 0000000..d0330e6
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/pbquote.py
@@ -0,0 +1,10 @@
+from twisted.spread import pb
+
+class QuoteReader(pb.Root):
+
+ def __init__(self, quoter):
+ self.quoter = quoter
+
+ def remote_nextQuote(self):
+ return self.quoter.getQuote()
+
diff --git a/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py b/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
new file mode 100644
index 0000000..c297539
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
@@ -0,0 +1,32 @@
+
+from sys import stdout
+from twisted.python import log
+log.discardLogs()
+from twisted.internet import reactor
+from twisted.spread import pb
+
+def connected(root):
+ root.callRemote('nextQuote').addCallbacks(success, failure)
+
+def success(quote):
+ stdout.write(quote + "\n")
+ reactor.stop()
+
+def failure(error):
+ stdout.write("Failed to obtain quote.\n")
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP(
+ "localhost", # host name
+ pb.portno, # port number
+ factory, # factory
+ )
+
+
+
+factory.getRootObject().addCallbacks(connected, # when we get the root
+ failure) # when we can't
+
+reactor.run() # start the main loop
+
diff --git a/doc/core/howto/listings/TwistedQuotes/quoteproto.py b/doc/core/howto/listings/TwistedQuotes/quoteproto.py
new file mode 100644
index 0000000..b8d3469
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quoteproto.py
@@ -0,0 +1,36 @@
+from zope.interface import Interface
+
+from twisted.internet.protocol import Factory, Protocol
+
+
+
+class IQuoter(Interface):
+ """
+ An object that returns quotes.
+ """
+ def getQuote():
+ """
+ Return a quote.
+ """
+
+
+
+class QOTD(Protocol):
+ def connectionMade(self):
+ self.transport.write(self.factory.quoter.getQuote()+'\r\n')
+ self.transport.loseConnection()
+
+
+
+class QOTDFactory(Factory):
+ """
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ """
+ protocol = QOTD
+
+ def __init__(self, quoter):
+ self.quoter = quoter
diff --git a/doc/core/howto/listings/TwistedQuotes/quoters.py b/doc/core/howto/listings/TwistedQuotes/quoters.py
new file mode 100644
index 0000000..f6d5689
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quoters.py
@@ -0,0 +1,39 @@
+from random import choice
+
+from zope.interface import implements
+
+from TwistedQuotes import quoteproto
+
+
+
+class StaticQuoter:
+ """
+ Return a static quote.
+ """
+
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, quote):
+ self.quote = quote
+
+
+ def getQuote(self):
+ return self.quote
+
+
+
+class FortuneQuoter:
+ """
+ Load quotes from a fortune-format file.
+ """
+ implements(quoteproto.IQuoter)
+
+ def __init__(self, filenames):
+ self.filenames = filenames
+
+
+ def getQuote(self):
+ quoteFile = file(choice(self.filenames))
+ quotes = quoteFile.read().split('\n%\n')
+ quoteFile.close()
+ return choice(quotes)
diff --git a/doc/core/howto/listings/TwistedQuotes/quotes.txt b/doc/core/howto/listings/TwistedQuotes/quotes.txt
new file mode 100644
index 0000000..62a5ed9
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotes.txt
@@ -0,0 +1,15 @@
+
+ the sysadmin of the future is going to know twisted-shelling like the back of his hand
+%
+ Ooh, I just figured out what my first twisted.reality creation will be.
+ Acapnotic: oh?
+ "Being Glyph Lefkowitz"
+%
+ Oh, please. Threads ownz j00.
+%
+ I used to hang out with this chick that ran a BBS.
+ She had a great baud.
+%
+ dsmith: Twisted is neat, but unfortunately, it's not object-oriented.
+%
+ twisted is madness
diff --git a/doc/core/howto/listings/TwistedQuotes/quotetap.py b/doc/core/howto/listings/TwistedQuotes/quotetap.py
new file mode 100644
index 0000000..06d15ec
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotetap.py
@@ -0,0 +1,29 @@
+from twisted.application import internet # services that run TCP/SSL/etc.
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+
+from twisted.python import usage # twisted command-line processing
+
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."]]
+
+
+def makeService(config):
+ """Return a service that will be attached to the application."""
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ return internet.TCPServer(port, factory)
diff --git a/doc/core/howto/listings/TwistedQuotes/quotetap2.py b/doc/core/howto/listings/TwistedQuotes/quotetap2.py
new file mode 100644
index 0000000..4bc0f06
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/quotetap2.py
@@ -0,0 +1,36 @@
+from TwistedQuotes import quoteproto # Protocol and Factory
+from TwistedQuotes import quoters # "give me a quote" code
+from TwistedQuotes import pbquote # perspective broker binding
+
+from twisted.application import service, internet
+from twisted.python import usage # twisted command-line processing
+from twisted.spread import pb # Perspective Broker
+
+class Options(usage.Options):
+ optParameters = [["port", "p", 8007,
+ "Port number to listen on for QOTD protocol."],
+ ["static", "s", "An apple a day keeps the doctor away.",
+ "A static quote to display."],
+ ["file", "f", None,
+ "A fortune-format text file to read quotes from."],
+ ["pb", "b", None,
+ "Port to listen with PB server"]]
+
+def makeService(config):
+ svc = service.MultiService()
+ if config["file"]: # If I was given a "file" option...
+ # Read quotes from a file, selecting a random one each time,
+ quoter = quoters.FortuneQuoter([config['file']])
+ else: # otherwise,
+ # read a single quote from the command line (or use the default).
+ quoter = quoters.StaticQuoter(config['static'])
+ port = int(config["port"]) # TCP port to listen on
+ factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
+ # Finally, set up our factory, with its custom quoter, to create QOTD
+ # protocol instances when events arrive on the specified port.
+ pbport = config['pb'] # TCP PB port to listen on
+ if pbport:
+ pbfact = pb.PBServerFactory(pbquote.QuoteReader(quoter))
+ svc.addService(internet.TCPServer(int(pbport), pbfact))
+ svc.addService(internet.TCPServer(port, factory))
+ return svc
diff --git a/doc/core/howto/listings/TwistedQuotes/webquote.rpy b/doc/core/howto/listings/TwistedQuotes/webquote.rpy
new file mode 100644
index 0000000..99e0e9c
--- /dev/null
+++ b/doc/core/howto/listings/TwistedQuotes/webquote.rpy
@@ -0,0 +1,12 @@
+# -*- Python -*-
+
+from TwistedQuotes import webquoteresource
+
+#__file__ is defined to be the name of this file; this is to
+#get the sibling file "quotes.txt" which should be in the same directory
+import os
+quotefile = os.path.join(os.path.split(__file__)[0], "quotes.txt")
+
+#ResourceScript requires us to define 'resource'.
+#This resource is used to render the page.
+resource = webquoteresource.QuoteResource([quotefile])
diff --git a/doc/core/howto/listings/amp/basic_client.py b/doc/core/howto/listings/amp/basic_client.py
new file mode 100644
index 0000000..6d99b68
--- /dev/null
+++ b/doc/core/howto/listings/amp/basic_client.py
@@ -0,0 +1,30 @@
+
+if __name__ == '__main__':
+ import basic_client
+ raise SystemExit(basic_client.main())
+
+from sys import stdout
+
+from twisted.python.log import startLogging, err
+from twisted.protocols.amp import AMP
+from twisted.internet import reactor
+from twisted.internet.protocol import Factory
+from twisted.internet.endpoints import TCP4ClientEndpoint
+
+def connect():
+ endpoint = TCP4ClientEndpoint(reactor, "127.0.0.1", 8750)
+ factory = Factory()
+ factory.protocol = AMP
+ return endpoint.connect(factory)
+
+
+def main():
+ startLogging(stdout)
+
+ d = connect()
+ d.addErrback(err, "Connection failed")
+ def done(ignored):
+ reactor.stop()
+ d.addCallback(done)
+
+ reactor.run()
diff --git a/doc/core/howto/listings/amp/basic_server.tac b/doc/core/howto/listings/amp/basic_server.tac
new file mode 100644
index 0000000..c28f663
--- /dev/null
+++ b/doc/core/howto/listings/amp/basic_server.tac
@@ -0,0 +1,14 @@
+from twisted.protocols.amp import AMP
+from twisted.internet import reactor
+from twisted.internet.protocol import Factory
+from twisted.internet.endpoints import TCP4ServerEndpoint
+from twisted.application.service import Application
+from twisted.application.internet import StreamServerEndpointService
+
+application = Application("basic AMP server")
+
+endpoint = TCP4ServerEndpoint(reactor, 8750)
+factory = Factory()
+factory.protocol = AMP
+service = StreamServerEndpointService(endpoint, factory)
+service.setServiceParent(application)
diff --git a/doc/core/howto/listings/amp/command_client.py b/doc/core/howto/listings/amp/command_client.py
new file mode 100644
index 0000000..8aa4cbd
--- /dev/null
+++ b/doc/core/howto/listings/amp/command_client.py
@@ -0,0 +1,48 @@
+
+if __name__ == '__main__':
+ import command_client
+ raise SystemExit(command_client.main())
+
+from sys import stdout
+
+from twisted.python.log import startLogging, err
+from twisted.protocols.amp import Integer, String, Unicode, Command
+from twisted.internet import reactor
+
+from basic_client import connect
+
+class UsernameUnavailable(Exception):
+ pass
+
+
+class RegisterUser(Command):
+ arguments = [('username', Unicode()),
+ ('publickey', String())]
+
+ response = [('uid', Integer())]
+
+ errors = {UsernameUnavailable: 'username-unavailable'}
+
+
+def main():
+ startLogging(stdout)
+
+ d = connect()
+ def connected(protocol):
+ return protocol.callRemote(
+ RegisterUser,
+ username=u'alice',
+ publickey='ssh-rsa AAAAB3NzaC1yc2 alice@actinium')
+ d.addCallback(connected)
+
+ def registered(result):
+ print 'Registration result:', result
+ d.addCallback(registered)
+
+ d.addErrback(err, "Failed to register")
+
+ def finished(ignored):
+ reactor.stop()
+ d.addCallback(finished)
+
+ reactor.run()
diff --git a/doc/core/howto/listings/application/service.tac b/doc/core/howto/listings/application/service.tac
new file mode 100644
index 0000000..b0167fa
--- /dev/null
+++ b/doc/core/howto/listings/application/service.tac
@@ -0,0 +1,34 @@
+# You can run this .tac file directly with:
+# twistd -ny service.tac
+
+"""
+This is an example .tac file which starts a webserver on port 8080 and
+serves files from the current working directory.
+
+The important part of this, the part that makes it a .tac file, is
+the final root-level section, which sets up the object called 'application'
+which twistd will look for
+"""
+
+import os
+from twisted.application import service, internet
+from twisted.web import static, server
+
+def getWebService():
+ """
+ Return a service suitable for creating an application object.
+
+ This service is a simple web server that serves files on port 8080 from
+ underneath the current working directory.
+ """
+ # create a resource to serve static files
+ fileServer = server.Site(static.File(os.getcwd()))
+ return internet.TCPServer(8080, fileServer)
+
+# this is the core part of any tac file, the creation of the root-level
+# application object
+application = service.Application("Demo application")
+
+# attach the service to its parent application
+service = getWebService()
+service.setServiceParent(application)
diff --git a/doc/core/howto/listings/deferred/synch-validation.py b/doc/core/howto/listings/deferred/synch-validation.py
new file mode 100644
index 0000000..2912f2b
--- /dev/null
+++ b/doc/core/howto/listings/deferred/synch-validation.py
@@ -0,0 +1,5 @@
+def synchronousIsValidUser(user):
+ '''
+ Return true if user is a valid user, false otherwise
+ '''
+ return user in ["Alice", "Angus", "Agnes"]
diff --git a/doc/core/howto/listings/pb/cache_classes.py b/doc/core/howto/listings/pb/cache_classes.py
new file mode 100755
index 0000000..0e3493e
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_classes.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class MasterDuckPond(pb.Cacheable):
+ def __init__(self, ducks):
+ self.observers = []
+ self.ducks = ducks
+ def count(self):
+ print "I have [%d] ducks" % len(self.ducks)
+ def addDuck(self, duck):
+ self.ducks.append(duck)
+ for o in self.observers: o.callRemote('addDuck', duck)
+ def removeDuck(self, duck):
+ self.ducks.remove(duck)
+ for o in self.observers: o.callRemote('removeDuck', duck)
+ def getStateToCacheAndObserveFor(self, perspective, observer):
+ self.observers.append(observer)
+ # you should ignore pb.Cacheable-specific state, like self.observers
+ return self.ducks # in this case, just a list of ducks
+ def stoppedObserving(self, perspective, observer):
+ self.observers.remove(observer)
+
+class SlaveDuckPond(pb.RemoteCache):
+ # This is a cache of a remote MasterDuckPond
+ def count(self):
+ return len(self.cacheducks)
+ def getDucks(self):
+ return self.cacheducks
+ def setCopyableState(self, state):
+ print " cache - sitting, er, setting ducks"
+ self.cacheducks = state
+ def observe_addDuck(self, newDuck):
+ print " cache - addDuck"
+ self.cacheducks.append(newDuck)
+ def observe_removeDuck(self, deadDuck):
+ print " cache - removeDuck"
+ self.cacheducks.remove(deadDuck)
+
+pb.setUnjellyableForClass(MasterDuckPond, SlaveDuckPond)
diff --git a/doc/core/howto/listings/pb/cache_receiver.py b/doc/core/howto/listings/pb/cache_receiver.py
new file mode 100755
index 0000000..b6d930b
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_receiver.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import cache_classes
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ self.pond = pond
+ print "got pond:", pond # a DuckPondCache
+ self.remote_checkDucks()
+ def remote_checkDucks(self):
+ print "[%d] ducks: " % self.pond.count(), self.pond.getDucks()
+ def remote_ignorePond(self):
+ # stop watching the pond
+ print "dropping pond"
+ # gc causes __del__ causes 'decache' msg causes stoppedObserving
+ self.pond = None
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/cache_sender.py b/doc/core/howto/listings/pb/cache_sender.py
new file mode 100755
index 0000000..391143a
--- /dev/null
+++ b/doc/core/howto/listings/pb/cache_sender.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from cache_classes import MasterDuckPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def phase1(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.phase2).addErrback(log.err)
+ def phase2(self, response):
+ self.pond.addDuck("ugly duckling")
+ self.pond.count()
+ reactor.callLater(1, self.phase3)
+ def phase3(self):
+ d = self.remote.callRemote("checkDucks")
+ d.addCallback(self.phase4).addErrback(log.err)
+ def phase4(self, dummy):
+ self.pond.removeDuck("one duck")
+ self.pond.count()
+ self.remote.callRemote("checkDucks")
+ d = self.remote.callRemote("ignorePond")
+ d.addCallback(self.phase5)
+ def phase5(self, dummy):
+ d = self.remote.callRemote("shutdown")
+ d.addCallback(self.phase6)
+ def phase6(self, dummy):
+ reactor.stop()
+
+def main():
+ master = MasterDuckPond(["one duck", "two duck"])
+ master.count()
+
+ sender = Sender(master)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.phase1)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/chatclient.py b/doc/core/howto/listings/pb/chatclient.py
new file mode 100755
index 0000000..eb2f677
--- /dev/null
+++ b/doc/core/howto/listings/pb/chatclient.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+class Client(pb.Referenceable):
+
+ def remote_print(self, message):
+ print message
+
+ def connect(self):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("alice", "1234"),
+ client=self)
+ def1.addCallback(self.connected)
+ reactor.run()
+
+ def connected(self, perspective):
+ print "connected, joining group #NeedAFourth"
+ # this perspective is a reference to our User object. Save a reference
+ # to it here, otherwise it will get garbage collected after this call,
+ # and the server will think we logged out.
+ self.perspective = perspective
+ d = perspective.callRemote("joinGroup", "#NeedAFourth")
+ d.addCallback(self.gotGroup)
+
+ def gotGroup(self, group):
+ print "joined group, now sending a message to all members"
+ # 'group' is a reference to the Group object (through a ViewPoint)
+ d = group.callRemote("send", "You can call me Al.")
+ d.addCallback(self.shutdown)
+
+ def shutdown(self, result):
+ reactor.stop()
+
+
+Client().connect()
+
diff --git a/doc/core/howto/listings/pb/chatserver.py b/doc/core/howto/listings/pb/chatserver.py
new file mode 100755
index 0000000..ff70d2a
--- /dev/null
+++ b/doc/core/howto/listings/pb/chatserver.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.cred import portal, checkers
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class ChatServer:
+ def __init__(self):
+ self.groups = {} # indexed by name
+
+ def joinGroup(self, groupname, user, allowMattress):
+ if not self.groups.has_key(groupname):
+ self.groups[groupname] = Group(groupname, allowMattress)
+ self.groups[groupname].addUser(user)
+ return self.groups[groupname]
+
+class ChatRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarID, mind, *interfaces):
+ assert pb.IPerspective in interfaces
+ avatar = User(avatarID)
+ avatar.server = self.server
+ avatar.attached(mind)
+ return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)
+
+class User(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def attached(self, mind):
+ self.remote = mind
+ def detached(self, mind):
+ self.remote = None
+ def perspective_joinGroup(self, groupname, allowMattress=True):
+ return self.server.joinGroup(groupname, self, allowMattress)
+ def send(self, message):
+ self.remote.callRemote("print", message)
+
+class Group(pb.Viewable):
+ def __init__(self, groupname, allowMattress):
+ self.name = groupname
+ self.allowMattress = allowMattress
+ self.users = []
+ def addUser(self, user):
+ self.users.append(user)
+ def view_send(self, from_user, message):
+ if not self.allowMattress and "mattress" in message:
+ raise ValueError, "Don't say that word"
+ for user in self.users:
+ user.send("<%s> says: %s" % (from_user.name, message))
+
+realm = ChatRealm()
+realm.server = ChatServer()
+checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+checker.addUser("alice", "1234")
+checker.addUser("bob", "secret")
+checker.addUser("carol", "fido")
+p = portal.Portal(realm, [checker])
+
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/copy2_classes.py b/doc/core/howto/listings/pb/copy2_classes.py
new file mode 100755
index 0000000..8b11e14
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_classes.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class FrogPond:
+ def __init__(self, numFrogs, numToads):
+ self.numFrogs = numFrogs
+ self.numToads = numToads
+ def count(self):
+ return self.numFrogs + self.numToads
+
+class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
diff --git a/doc/core/howto/listings/pb/copy2_receiver.py b/doc/core/howto/listings/pb/copy2_receiver.py
new file mode 100755
index 0000000..76f06c3
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_receiver.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+import copy2_classes # needed to get ReceiverPond registered with Jelly
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ print " count %d" % pond.count()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/copy2_sender.py b/doc/core/howto/listings/pb/copy2_sender.py
new file mode 100755
index 0000000..5b008b7
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy2_sender.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+from copy2_classes import SenderPond
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, obj):
+ d = obj.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ pond = SenderPond(3, 4)
+ print "count %d" % pond.count()
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/doc/core/howto/listings/pb/copy_receiver.tac b/doc/core/howto/listings/pb/copy_receiver.tac
new file mode 100755
index 0000000..dc9905e
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy_receiver.tac
@@ -0,0 +1,41 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+"""
+
+import sys
+if __name__ == '__main__':
+ print __doc__
+ sys.exit(1)
+
+from twisted.application import service, internet
+from twisted.internet import reactor
+from twisted.spread import pb
+from copy_sender import LilyPond, CopyPond
+
+from twisted.python import log
+#log.startLogging(sys.stdout)
+
+class ReceiverPond(pb.RemoteCopy, LilyPond):
+ pass
+pb.setUnjellyableForClass(CopyPond, ReceiverPond)
+
+class Receiver(pb.Root):
+ def remote_takePond(self, pond):
+ print " got pond:", pond
+ pond.countFrogs()
+ return "safe and sound" # positive acknowledgement
+ def remote_shutdown(self):
+ reactor.stop()
+
+application = service.Application("copy_receiver")
+internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/listings/pb/copy_sender.py b/doc/core/howto/listings/pb/copy_sender.py
new file mode 100755
index 0000000..fca0594
--- /dev/null
+++ b/doc/core/howto/listings/pb/copy_sender.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class LilyPond:
+ def setStuff(self, color, numFrogs):
+ self.color = color
+ self.numFrogs = numFrogs
+ def countFrogs(self):
+ print "%d frogs" % self.numFrogs
+
+class CopyPond(LilyPond, pb.Copyable):
+ pass
+
+class Sender:
+ def __init__(self, pond):
+ self.pond = pond
+
+ def got_obj(self, remote):
+ self.remote = remote
+ d = remote.callRemote("takePond", self.pond)
+ d.addCallback(self.ok).addErrback(self.notOk)
+
+ def ok(self, response):
+ print "pond arrived", response
+ reactor.stop()
+ def notOk(self, failure):
+ print "error during takePond:"
+ if failure.type == jelly.InsecureJelly:
+ print " InsecureJelly"
+ else:
+ print failure
+ reactor.stop()
+ return None
+
+def main():
+ from copy_sender import CopyPond # so it's not __main__.CopyPond
+ pond = CopyPond()
+ pond.setStuff("green", 7)
+ pond.countFrogs()
+ # class name:
+ print ".".join([pond.__class__.__module__, pond.__class__.__name__])
+
+ sender = Sender(pond)
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ deferred = factory.getRootObject()
+ deferred.addCallback(sender.got_obj)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/exc_client.py b/doc/core/howto/listings/pb/exc_client.py
new file mode 100755
index 0000000..cedd107
--- /dev/null
+++ b/doc/core/howto/listings/pb/exc_client.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ d = factory.getRootObject()
+ d.addCallbacks(got_obj)
+ reactor.run()
+
+def got_obj(obj):
+ # change "broken" into "broken2" to demonstrate an unhandled exception
+ d2 = obj.callRemote("broken")
+ d2.addCallback(working)
+ d2.addErrback(broken)
+
+def working():
+ print "erm, it wasn't *supposed* to work.."
+
+def broken(reason):
+ print "got remote Exception"
+ # reason should be a Failure (or subclass) holding the MyError exception
+ print " .__class__ =", reason.__class__
+ print " .getErrorMessage() =", reason.getErrorMessage()
+ print " .type =", reason.type
+ reactor.stop()
+
+main()
diff --git a/doc/core/howto/listings/pb/exc_server.py b/doc/core/howto/listings/pb/exc_server.py
new file mode 100755
index 0000000..9cb3dab
--- /dev/null
+++ b/doc/core/howto/listings/pb/exc_server.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class MyError(pb.Error):
+ """This is an Expected Exception. Something bad happened."""
+ pass
+
+class MyError2(Exception):
+ """This is an Unexpected Exception. Something really bad happened."""
+ pass
+
+class One(pb.Root):
+ def remote_broken(self):
+ msg = "fall down go boom"
+ print "raising a MyError exception with data '%s'" % msg
+ raise MyError(msg)
+ def remote_broken2(self):
+ msg = "hadda owie"
+ print "raising a MyError2 exception with data '%s'" % msg
+ raise MyError2(msg)
+
+def main():
+ reactor.listenTCP(8800, pb.PBServerFactory(One()))
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/pb1client.py b/doc/core/howto/listings/pb/pb1client.py
new file mode 100755
index 0000000..8525f16
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb1client.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallbacks(got_obj1, err_obj1)
+ reactor.run()
+
+def err_obj1(reason):
+ print "error getting first object", reason
+ reactor.stop()
+
+def got_obj1(obj1):
+ print "got first object:", obj1
+ print "asking it to getTwo"
+ def2 = obj1.callRemote("getTwo")
+ def2.addCallbacks(got_obj2)
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling it to do three(12)"
+ obj2.callRemote("three", 12)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb1server.py b/doc/core/howto/listings/pb/pb1server.py
new file mode 100755
index 0000000..d927489
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb1server.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+
+class Two(pb.Referenceable):
+ def remote_three(self, arg):
+ print "Two.three was given", arg
+
+class One(pb.Root):
+ def remote_getTwo(self):
+ two = Two()
+ print "returning a Two called", two
+ return two
+
+from twisted.internet import reactor
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb2client.py b/doc/core/howto/listings/pb/pb2client.py
new file mode 100755
index 0000000..0164447
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb2client.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ foo = Foo()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ factory.getRootObject().addCallback(foo.step1)
+ reactor.run()
+
+# keeping globals around is starting to get ugly, so we use a simple class
+# instead. Instead of hooking one function to the next, we hook one method
+# to the next.
+
+class Foo:
+ def __init__(self):
+ self.oneRef = None
+
+ def step1(self, obj):
+ print "got one object:", obj
+ self.oneRef = obj
+ print "asking it to getTwo"
+ self.oneRef.callRemote("getTwo").addCallback(self.step2)
+
+ def step2(self, two):
+ print "got two object:", two
+ print "giving it back to one"
+ print "one is", self.oneRef
+ self.oneRef.callRemote("checkTwo", two)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb2server.py b/doc/core/howto/listings/pb/pb2server.py
new file mode 100755
index 0000000..c32344e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb2server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "two.print was given", arg
+
+class One(pb.Root):
+ def __init__(self, two):
+ #pb.Root.__init__(self) # pb.Root doesn't implement __init__
+ self.two = two
+ def remote_getTwo(self):
+ print "One.getTwo(), returning my two called", self.two
+ return self.two
+ def remote_checkTwo(self, newtwo):
+ print "One.checkTwo(): comparing my two", self.two
+ print "One.checkTwo(): against your two", newtwo
+ if self.two == newtwo:
+ print "One.checkTwo(): our twos are the same"
+
+
+two = Two()
+root_obj = One(two)
+reactor.listenTCP(8800, pb.PBServerFactory(root_obj))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb3client.py b/doc/core/howto/listings/pb/pb3client.py
new file mode 100755
index 0000000..28a9749
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb3client.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Two(pb.Referenceable):
+ def remote_print(self, arg):
+ print "Two.print() called with", arg
+
+def main():
+ two = Two()
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.getRootObject()
+ def1.addCallback(got_obj, two) # hands our 'two' to the callback
+ reactor.run()
+
+def got_obj(obj, two):
+ print "got One:", obj
+ print "giving it our two"
+ obj.callRemote("takeTwo", two)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb3server.py b/doc/core/howto/listings/pb/pb3server.py
new file mode 100755
index 0000000..f71b4a1
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb3server.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class One(pb.Root):
+ def remote_takeTwo(self, two):
+ print "received a Two called", two
+ print "telling it to print(12)"
+ two.callRemote("print", 12)
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb4client.py b/doc/core/howto/listings/pb/pb4client.py
new file mode 100755
index 0000000..10f2694
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb4client.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def main():
+ rootobj_def = pb.getObjectAt("localhost", 8800, 30)
+ rootobj_def.addCallbacks(got_rootobj)
+ obj2_def = getSomeObjectAt("localhost", 8800, 30, "two")
+ obj2_def.addCallbacks(got_obj2)
+ obj3_def = getSomeObjectAt("localhost", 8800, 30, "three")
+ obj3_def.addCallbacks(got_obj3)
+ reactor.run()
+
+def got_rootobj(rootobj):
+ print "got root object:", rootobj
+ print "telling root object to do foo(A)"
+ rootobj.callRemote("foo", "A")
+
+def got_obj2(obj2):
+ print "got second object:", obj2
+ print "telling second object to do foo(B)"
+ obj2.callRemote("foo", "B")
+
+def got_obj3(obj3):
+ print "got third object:", obj3
+ print "telling third object to do foo(C)"
+ obj3.callRemote("foo", "C")
+
+class my_ObjectRetrieval(pb._ObjectRetrieval):
+ def __init__(self, broker, d, objname):
+ pb._ObjectRetrieval.__init__(self, broker, d)
+ self.objname = objname
+ def connectionMade(self):
+ assert not self.term, "How did this get called?"
+ x = self.broker.remoteForName(self.objname)
+ del self.broker
+ self.term = 1
+ self.deferred.callback(x)
+
+def getSomeObjectAt(host, port, timeout=None, objname="root"):
+ from twisted.internet import defer
+ from twisted.spread.pb import Broker, BrokerClientFactory
+ d = defer.Deferred()
+ b = Broker(1)
+ bf = BrokerClientFactory(b)
+ my_ObjectRetrieval(b, d, objname)
+ if host == "unix":
+ # every time you use this, God kills a kitten
+ reactor.connectUNIX(port, bf, timeout)
+ else:
+ reactor.connectTCP(host, port, bf, timeout)
+ return d
+
+main()
diff --git a/doc/core/howto/listings/pb/pb5client.py b/doc/core/howto/listings/pb/pb5client.py
new file mode 100755
index 0000000..3026780
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb5client.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective ref:", perspective
+ print "asking it to foo(12)"
+ perspective.callRemote("foo", 12)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb5server.py b/doc/core/howto/listings/pb/pb5server.py
new file mode 100755
index 0000000..dc95f6e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb5server.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+p.registerChecker(
+ checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1"))
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb6client1.py b/doc/core/howto/listings/pb/pb6client1.py
new file mode 100755
index 0000000..38ae65a
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6client1.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user1", "pass1"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ perspective.callRemote("foo", 13)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb6client2.py b/doc/core/howto/listings/pb/pb6client2.py
new file mode 100755
index 0000000..ecceb2a
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6client2.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+from twisted.spread import pb
+from twisted.internet import reactor
+from twisted.cred import credentials
+
+def main():
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+ def1 = factory.login(credentials.UsernamePassword("user2", "pass2"))
+ def1.addCallback(connected)
+ reactor.run()
+
+def connected(perspective):
+ print "got perspective2 ref:", perspective
+ print "asking it to foo(14)"
+ perspective.callRemote("foo", 14)
+
+main()
diff --git a/doc/core/howto/listings/pb/pb6server.py b/doc/core/howto/listings/pb/pb6server.py
new file mode 100755
index 0000000..9c98d5e
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb6server.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from zope.interface import implements
+
+from twisted.spread import pb
+from twisted.cred import checkers, portal
+from twisted.internet import reactor
+
+class MyPerspective(pb.Avatar):
+ def __init__(self, name):
+ self.name = name
+ def perspective_foo(self, arg):
+ print "I am", self.name, "perspective_foo(",arg,") called on", self
+
+class MyRealm:
+ implements(portal.IRealm)
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if pb.IPerspective not in interfaces:
+ raise NotImplementedError
+ return pb.IPerspective, MyPerspective(avatarId), lambda:None
+
+p = portal.Portal(MyRealm())
+c = checkers.InMemoryUsernamePasswordDatabaseDontUse(user1="pass1",
+ user2="pass2")
+p.registerChecker(c)
+reactor.listenTCP(8800, pb.PBServerFactory(p))
+reactor.run()
diff --git a/doc/core/howto/listings/pb/pb7client.py b/doc/core/howto/listings/pb/pb7client.py
new file mode 100755
index 0000000..e212fb2
--- /dev/null
+++ b/doc/core/howto/listings/pb/pb7client.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def one(port, user, pw, service, perspective, number):
+ factory = pb.PBClientFactory()
+ reactor.connectTCP("localhost", port, factory)
+ def1 = factory.getPerspective(
+ user, pw, service, perspective)
+ def1.addCallback(connected, number)
+
+def connected(perspective, number):
+ print "got perspective ref:", perspective
+ print "asking it to foo(%d)" % number
+ perspective.callRemote("foo", number)
+
+def main():
+ one(8800, "user1", "pass1", "service1", "perspective1.1", 10)
+ one(8800, "user1", "pass1", "service2", "perspective2.1", 11)
+ one(8800, "user2", "pass2", "service1", "perspective1.2", 12)
+ one(8800, "user2", "pass2", "service2", "perspective2.2", 13)
+ one(8801, "user3", "pass3", "service3", "perspective3.3", 14)
+ reactor.run()
+
+main()
diff --git a/doc/core/howto/listings/pb/pbAnonClient.py b/doc/core/howto/listings/pb/pbAnonClient.py
new file mode 100755
index 0000000..fbd9a9f
--- /dev/null
+++ b/doc/core/howto/listings/pb/pbAnonClient.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+"""
+
+from sys import stdout
+
+from twisted.python.log import err, startLogging
+from twisted.cred.credentials import Anonymous, UsernamePassword
+from twisted.internet import reactor
+from twisted.internet.defer import gatherResults
+from twisted.spread.pb import PBClientFactory
+
+
+def error(why, msg):
+ """
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ """
+ err(why, msg)
+
+
+def connected(perspective):
+ """
+ Login callback which invokes the remote "foo" method on the perspective
+ which the server returned.
+ """
+ print "got perspective1 ref:", perspective
+ print "asking it to foo(13)"
+ return perspective.callRemote("foo", 13)
+
+
+def finished(ignored):
+ """
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ """
+ reactor.stop()
+
+
+def main():
+ """
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ """
+ startLogging(stdout)
+ factory = PBClientFactory()
+ reactor.connectTCP("localhost", 8800, factory)
+
+ anonymousLogin = factory.login(Anonymous())
+ anonymousLogin.addCallback(connected)
+ anonymousLogin.addErrback(error, "Anonymous login failed")
+
+ usernameLogin = factory.login(UsernamePassword("user1", "pass1"))
+ usernameLogin.addCallback(connected)
+ usernameLogin.addErrback(error, "Username/password login failed")
+
+ bothDeferreds = gatherResults([anonymousLogin, usernameLogin])
+ bothDeferreds.addCallback(finished)
+
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/pbAnonServer.py b/doc/core/howto/listings/pb/pbAnonServer.py
new file mode 100755
index 0000000..f5eadac
--- /dev/null
+++ b/doc/core/howto/listings/pb/pbAnonServer.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name "Anonymous".
+"""
+
+from sys import stdout
+
+from zope.interface import implements
+
+from twisted.python.log import startLogging
+from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess
+from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted.cred.portal import IRealm, Portal
+from twisted.internet import reactor
+from twisted.spread.pb import Avatar, IPerspective, PBServerFactory
+
+
+class MyPerspective(Avatar):
+ """
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{"Anonymous"}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named "Anonymous").
+ """
+ def __init__(self, name):
+ self.name = name
+
+
+ def perspective_foo(self, arg):
+ """
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ """
+ print "I am %s. perspective_foo(%s) called on %s." % (
+ self.name, arg, self)
+
+
+
+class MyRealm(object):
+ """
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ """
+ implements(IRealm)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+ if IPerspective not in interfaces:
+ raise NotImplementedError("MyRealm only handles IPerspective")
+ if avatarId is ANONYMOUS:
+ avatarId = "Anonymous"
+ return IPerspective, MyPerspective(avatarId), lambda: None
+
+
+
+def main():
+ """
+ Create a PB server using MyRealm and run it on port 8800.
+ """
+ startLogging(stdout)
+
+ p = Portal(MyRealm())
+
+ # Here the username/password checker is registered.
+ c1 = InMemoryUsernamePasswordDatabaseDontUse(user1="pass1", user2="pass2")
+ p.registerChecker(c1)
+
+ # Here the anonymous checker is registered.
+ c2 = AllowAnonymousAccess()
+ p.registerChecker(c2)
+
+ reactor.listenTCP(8800, PBServerFactory(p))
+ reactor.run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/pb/trap_client.py b/doc/core/howto/listings/pb/trap_client.py
new file mode 100755
index 0000000..7fb2a9a
--- /dev/null
+++ b/doc/core/howto/listings/pb/trap_client.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.spread import pb, jelly
+from twisted.python import log
+from twisted.internet import reactor
+
+class MyException(pb.Error): pass
+class MyOtherException(pb.Error): pass
+
+class ScaryObject:
+ # not safe for serialization
+ pass
+
+def worksLike(obj):
+ # the callback/errback sequence in class One works just like an
+ # asynchronous version of the following:
+ try:
+ response = obj.callMethod(name, arg)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+ except jelly.InsecureJelly:
+ print " InsecureJelly: you tried to send something unsafe to them"
+ except (MyException, MyOtherException):
+ print " remote raised a MyException" # or MyOtherException
+ except:
+ print " something else happened"
+ else:
+ print " method successful, response:", response
+
+class One:
+ def worked(self, response):
+ print " method successful, response:", response
+ def check_InsecureJelly(self, failure):
+ failure.trap(jelly.InsecureJelly)
+ print " InsecureJelly: you tried to send something unsafe to them"
+ return None
+ def check_MyException(self, failure):
+ which = failure.trap(MyException, MyOtherException)
+ if which == MyException:
+ print " remote raised a MyException"
+ else:
+ print " remote raised a MyOtherException"
+ return None
+ def catch_everythingElse(self, failure):
+ print " something else happened"
+ log.err(failure)
+ return None
+
+ def doCall(self, explanation, arg):
+ print explanation
+ try:
+ deferred = self.remote.callRemote("fooMethod", arg)
+ deferred.addCallback(self.worked)
+ deferred.addErrback(self.check_InsecureJelly)
+ deferred.addErrback(self.check_MyException)
+ deferred.addErrback(self.catch_everythingElse)
+ except pb.DeadReferenceError:
+ print " stale reference: the client disconnected or crashed"
+
+ def callOne(self):
+ self.doCall("callOne: call with safe object", "safe string")
+ def callTwo(self):
+ self.doCall("callTwo: call with dangerous object", ScaryObject())
+ def callThree(self):
+ self.doCall("callThree: call that raises remote exception", "panic!")
+ def callShutdown(self):
+ print "telling them to shut down"
+ self.remote.callRemote("shutdown")
+ def callFour(self):
+ self.doCall("callFour: call on stale reference", "dummy")
+
+ def got_obj(self, obj):
+ self.remote = obj
+ reactor.callLater(1, self.callOne)
+ reactor.callLater(2, self.callTwo)
+ reactor.callLater(3, self.callThree)
+ reactor.callLater(4, self.callShutdown)
+ reactor.callLater(5, self.callFour)
+ reactor.callLater(6, reactor.stop)
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("localhost", 8800, factory)
+deferred = factory.getRootObject()
+deferred.addCallback(One().got_obj)
+reactor.run()
diff --git a/doc/core/howto/listings/pb/trap_server.py b/doc/core/howto/listings/pb/trap_server.py
new file mode 100755
index 0000000..03bd92c
--- /dev/null
+++ b/doc/core/howto/listings/pb/trap_server.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import reactor
+from twisted.spread import pb
+
+class MyException(pb.Error):
+ pass
+
+class One(pb.Root):
+ def remote_fooMethod(self, arg):
+ if arg == "panic!":
+ raise MyException
+ return "response"
+ def remote_shutdown(self):
+ reactor.stop()
+
+reactor.listenTCP(8800, pb.PBServerFactory(One()))
+reactor.run()
diff --git a/doc/core/howto/listings/process/process.py b/doc/core/howto/listings/process/process.py
new file mode 100755
index 0000000..95579e5
--- /dev/null
+++ b/doc/core/howto/listings/process/process.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+from twisted.internet import protocol
+from twisted.internet import reactor
+import re
+
+class MyPP(protocol.ProcessProtocol):
+ def __init__(self, verses):
+ self.verses = verses
+ self.data = ""
+ def connectionMade(self):
+ print "connectionMade!"
+ for i in range(self.verses):
+ self.transport.write("Aleph-null bottles of beer on the wall,\n" +
+ "Aleph-null bottles of beer,\n" +
+ "Take one down and pass it around,\n" +
+ "Aleph-null bottles of beer on the wall.\n")
+ self.transport.closeStdin() # tell them we're done
+ def outReceived(self, data):
+ print "outReceived! with %d bytes!" % len(data)
+ self.data = self.data + data
+ def errReceived(self, data):
+ print "errReceived! with %d bytes!" % len(data)
+ def inConnectionLost(self):
+ print "inConnectionLost! stdin is closed! (we probably did it)"
+ def outConnectionLost(self):
+ print "outConnectionLost! The child closed their stdout!"
+ # now is the time to examine what they wrote
+ #print "I saw them write:", self.data
+ (dummy, lines, words, chars, file) = re.split(r'\s+', self.data)
+ print "I saw %s lines" % lines
+ def errConnectionLost(self):
+ print "errConnectionLost! The child closed their stderr."
+ def processExited(self, reason):
+ print "processExited, status %d" % (reason.value.exitCode,)
+ def processEnded(self, reason):
+ print "processEnded, status %d" % (reason.value.exitCode,)
+ print "quitting"
+ reactor.stop()
+
+pp = MyPP(10)
+reactor.spawnProcess(pp, "wc", ["wc"], {})
+reactor.run()
diff --git a/doc/core/howto/listings/process/quotes.py b/doc/core/howto/listings/process/quotes.py
new file mode 100644
index 0000000..c0efeaf
--- /dev/null
+++ b/doc/core/howto/listings/process/quotes.py
@@ -0,0 +1,25 @@
+from twisted.internet import protocol, utils, reactor
+from twisted.python import failure
+from cStringIO import StringIO
+
+class FortuneQuoter(protocol.Protocol):
+
+ fortune = '/usr/games/fortune'
+
+ def connectionMade(self):
+ output = utils.getProcessOutput(self.fortune)
+ output.addCallbacks(self.writeResponse, self.noResponse)
+
+ def writeResponse(self, resp):
+ self.transport.write(resp)
+ self.transport.loseConnection()
+
+ def noResponse(self, err):
+ self.transport.loseConnection()
+
+
+if __name__ == '__main__':
+ f = protocol.Factory()
+ f.protocol = FortuneQuoter
+ reactor.listenTCP(10999, f)
+ reactor.run()
diff --git a/doc/core/howto/listings/process/trueandfalse.py b/doc/core/howto/listings/process/trueandfalse.py
new file mode 100644
index 0000000..4962c93
--- /dev/null
+++ b/doc/core/howto/listings/process/trueandfalse.py
@@ -0,0 +1,14 @@
+from twisted.internet import utils, reactor
+
+def printTrueValue(val):
+ print "/bin/true exits with rc=%d" % val
+ output = utils.getProcessValue('/bin/false')
+ output.addCallback(printFalseValue)
+
+def printFalseValue(val):
+ print "/bin/false exits with rc=%d" % val
+ reactor.stop()
+
+output = utils.getProcessValue('/bin/true')
+output.addCallback(printTrueValue)
+reactor.run()
diff --git a/doc/core/howto/listings/sendmsg/copy_descriptor.py b/doc/core/howto/listings/sendmsg/copy_descriptor.py
new file mode 100644
index 0000000..1c9a774
--- /dev/null
+++ b/doc/core/howto/listings/sendmsg/copy_descriptor.py
@@ -0,0 +1,35 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Demonstration of copying a file descriptor over an AF_UNIX connection using
+sendmsg.
+"""
+
+from os import pipe, read, write
+from socket import SOL_SOCKET, socketpair
+from struct import unpack, pack
+
+from twisted.python.sendmsg import SCM_RIGHTS, send1msg, recv1msg
+
+def main():
+ foo, bar = socketpair()
+ reader, writer = pipe()
+
+ # Send a copy of the descriptor. Notice that there must be at least one
+ # byte of normal data passed in.
+ sent = send1msg(
+ foo.fileno(), "\x00", 0,
+ [(SOL_SOCKET, SCM_RIGHTS, pack("i", reader))])
+
+ # Receive the copy, including that one byte of normal data.
+ data, flags, ancillary = recv1msg(bar.fileno(), 1024)
+ duplicate = unpack("i", ancillary[0][2])[0]
+
+ # Demonstrate that the copy works just like the original
+ write(writer, "Hello, world")
+ print "Read from original (%d): %r" % (reader, read(reader, 6))
+ print "Read from duplicate (%d): %r" % (duplicate, read(duplicate, 6))
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/sendmsg/send_replacement.py b/doc/core/howto/listings/sendmsg/send_replacement.py
new file mode 100644
index 0000000..9460c8e
--- /dev/null
+++ b/doc/core/howto/listings/sendmsg/send_replacement.py
@@ -0,0 +1,21 @@
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Demonstration of sending bytes over a TCP connection using sendmsg.
+"""
+
+from socket import socketpair
+
+from twisted.python.sendmsg import send1msg, recv1msg
+
+def main():
+ foo, bar = socketpair()
+ sent = send1msg(foo.fileno(), "Hello, world")
+ print "Sent", sent, "bytes"
+ (received, flags, ancillary) = recv1msg(bar.fileno(), 1024)
+ print "Received", repr(received)
+ print "Extra stuff, boring in this case", flags, ancillary
+
+if __name__ == '__main__':
+ main()
diff --git a/doc/core/howto/listings/servers/chat.py b/doc/core/howto/listings/servers/chat.py
new file mode 100644
index 0000000..01795bf
--- /dev/null
+++ b/doc/core/howto/listings/servers/chat.py
@@ -0,0 +1,51 @@
+from twisted.internet.protocol import Factory
+from twisted.protocols.basic import LineReceiver
+from twisted.internet import reactor
+
+class Chat(LineReceiver):
+
+ def __init__(self, users):
+ self.users = users
+ self.name = None
+ self.state = "GETNAME"
+
+ def connectionMade(self):
+ self.sendLine("What's your name?")
+
+ def connectionLost(self, reason):
+ if self.users.has_key(self.name):
+ del self.users[self.name]
+
+ def lineReceived(self, line):
+ if self.state == "GETNAME":
+ self.handle_GETNAME(line)
+ else:
+ self.handle_CHAT(line)
+
+ def handle_GETNAME(self, name):
+ if self.users.has_key(name):
+ self.sendLine("Name taken, please choose another.")
+ return
+ self.sendLine("Welcome, %s!" % (name,))
+ self.name = name
+ self.users[name] = self
+ self.state = "CHAT"
+
+ def handle_CHAT(self, message):
+ message = "<%s> %s" % (self.name, message)
+ for name, protocol in self.users.iteritems():
+ if protocol != self:
+ protocol.sendLine(message)
+
+
+class ChatFactory(Factory):
+
+ def __init__(self):
+ self.users = {} # maps user names to Chat instances
+
+ def buildProtocol(self, addr):
+ return Chat(self.users)
+
+
+reactor.listenTCP(8123, ChatFactory())
+reactor.run()
diff --git a/doc/core/howto/listings/trial/calculus/__init__.py b/doc/core/howto/listings/trial/calculus/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/doc/core/howto/listings/trial/calculus/base_1.py b/doc/core/howto/listings/trial/calculus/base_1.py
new file mode 100644
index 0000000..e827263
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_1.py
@@ -0,0 +1,16 @@
+# -*- test-case-name: calculus.test.test_base_1 -*-
+
+
+
+class Calculation(object):
+ def add(self, a, b):
+ pass
+
+ def subtract(self, a, b):
+ pass
+
+ def multiply(self, a, b):
+ pass
+
+ def divide(self, a, b):
+ pass
diff --git a/doc/core/howto/listings/trial/calculus/base_2.py b/doc/core/howto/listings/trial/calculus/base_2.py
new file mode 100644
index 0000000..9644912
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_2.py
@@ -0,0 +1,14 @@
+# -*- test-case-name: calculus.test.test_base_2 -*-
+
+class Calculation(object):
+ def add(self, a, b):
+ return a + b
+
+ def subtract(self, a, b):
+ return a - b
+
+ def multiply(self, a, b):
+ return a * b
+
+ def divide(self, a, b):
+ return a / b
diff --git a/doc/core/howto/listings/trial/calculus/base_3.py b/doc/core/howto/listings/trial/calculus/base_3.py
new file mode 100644
index 0000000..bd33c31
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/base_3.py
@@ -0,0 +1,24 @@
+# -*- test-case-name: calculus.test.test_base_3 -*-
+
+class Calculation(object):
+ def _make_ints(self, *args):
+ try:
+ return map(int, args)
+ except ValueError:
+ raise TypeError("Couldn't coerce arguments to integers: %s" % args)
+
+ def add(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a + b
+
+ def subtract(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a - b
+
+ def multiply(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a * b
+
+ def divide(self, a, b):
+ a, b = self._make_ints(a, b)
+ return a / b
diff --git a/doc/core/howto/listings/trial/calculus/client_1.py b/doc/core/howto/listings/trial/calculus/client_1.py
new file mode 100644
index 0000000..a42434d
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_1.py
@@ -0,0 +1,39 @@
+# -*- test-case-name: calculus.test.test_client_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import defer
+
+
+
+class RemoteCalculationClient(basic.LineReceiver):
+ def __init__(self):
+ self.results = []
+
+
+ def lineReceived(self, line):
+ d = self.results.pop(0)
+ d.callback(int(line))
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ self.results.append(d)
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/client_2.py b/doc/core/howto/listings/trial/calculus/client_2.py
new file mode 100644
index 0000000..dfb464b
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_2.py
@@ -0,0 +1,54 @@
+# -*- test-case-name: calculus.test.test_client_2 -*-
+
+from twisted.protocols import basic
+from twisted.internet import defer, reactor
+
+
+
+class ClientTimeoutError(Exception):
+ pass
+
+
+
+class RemoteCalculationClient(basic.LineReceiver):
+
+ callLater = reactor.callLater
+ timeOut = 60
+
+ def __init__(self):
+ self.results = []
+
+
+ def lineReceived(self, line):
+ d, callID = self.results.pop(0)
+ callID.cancel()
+ d.callback(int(line))
+
+
+ def _cancel(self, d):
+ d.errback(ClientTimeoutError())
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ callID = self.callLater(self.timeOut, self._cancel, d)
+ self.results.append((d, callID))
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/client_3.py b/doc/core/howto/listings/trial/calculus/client_3.py
new file mode 100644
index 0000000..31b0c35
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/client_3.py
@@ -0,0 +1,53 @@
+# -*- test-case-name: calculus.test.test_client -*-
+
+from twisted.protocols import basic, policies
+from twisted.internet import defer
+
+
+
+class ClientTimeoutError(Exception):
+ pass
+
+
+
+class RemoteCalculationClient(object, basic.LineReceiver, policies.TimeoutMixin):
+
+ def __init__(self):
+ self.results = []
+ self._timeOut = 60
+
+ def lineReceived(self, line):
+ self.setTimeout(None)
+ d = self.results.pop(0)
+ d.callback(int(line))
+
+
+ def timeoutConnection(self):
+ for d in self.results:
+ d.errback(ClientTimeoutError())
+ self.transport.loseConnection()
+
+
+ def _sendOperation(self, op, a, b):
+ d = defer.Deferred()
+ self.results.append(d)
+ line = "%s %d %d" % (op, a, b)
+ self.sendLine(line)
+ self.setTimeout(self._timeOut)
+ return d
+
+
+ def add(self, a, b):
+ return self._sendOperation("add", a, b)
+
+
+ def subtract(self, a, b):
+ return self._sendOperation("subtract", a, b)
+
+
+ def multiply(self, a, b):
+ return self._sendOperation("multiply", a, b)
+
+
+ def divide(self, a, b):
+ return self._sendOperation("divide", a, b)
diff --git a/doc/core/howto/listings/trial/calculus/remote_1.py b/doc/core/howto/listings/trial/calculus/remote_1.py
new file mode 100644
index 0000000..4fcdd74
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/remote_1.py
@@ -0,0 +1,47 @@
+# -*- test-case-name: calculus.test.test_remote_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from calculus.base_3 import Calculation
+
+
+
+class CalculationProxy(object):
+ def __init__(self):
+ self.calc = Calculation()
+ for m in ['add', 'subtract', 'multiply', 'divide']:
+ setattr(self, 'remote_%s' % m, getattr(self.calc, m))
+
+
+
+class RemoteCalculationProtocol(basic.LineReceiver):
+ def __init__(self):
+ self.proxy = CalculationProxy()
+
+
+ def lineReceived(self, line):
+ op, a, b = line.split()
+ a = int(a)
+ b = int(b)
+ op = getattr(self.proxy, 'remote_%s' % (op,))
+ result = op(a, b)
+ self.sendLine(str(result))
+
+
+
+class RemoteCalculationFactory(protocol.Factory):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(0, RemoteCalculationFactory())
+ reactor.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/core/howto/listings/trial/calculus/remote_2.py b/doc/core/howto/listings/trial/calculus/remote_2.py
new file mode 100644
index 0000000..6826be1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/remote_2.py
@@ -0,0 +1,51 @@
+# -*- test-case-name: calculus.test.test_remote_1 -*-
+
+from twisted.protocols import basic
+from twisted.internet import protocol
+from twisted.python import log
+from calculus.base_3 import Calculation
+
+
+
+class CalculationProxy(object):
+ def __init__(self):
+ self.calc = Calculation()
+ for m in ['add', 'subtract', 'multiply', 'divide']:
+ setattr(self, 'remote_%s' % m, getattr(self.calc, m))
+
+
+
+class RemoteCalculationProtocol(basic.LineReceiver):
+ def __init__(self):
+ self.proxy = CalculationProxy()
+
+
+ def lineReceived(self, line):
+ op, a, b = line.split()
+ op = getattr(self.proxy, 'remote_%s' % (op,))
+ try:
+ result = op(a, b)
+ except TypeError:
+ log.err()
+ self.sendLine("error")
+ else:
+ self.sendLine(str(result))
+
+
+
+class RemoteCalculationFactory(protocol.Factory):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main():
+ from twisted.internet import reactor
+ from twisted.python import log
+ import sys
+ log.startLogging(sys.stdout)
+ reactor.listenTCP(0, RemoteCalculationFactory())
+ reactor.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/core/howto/listings/trial/calculus/test/__init__.py b/doc/core/howto/listings/trial/calculus/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_1.py b/doc/core/howto/listings/trial/calculus/test/test_base_1.py
new file mode 100644
index 0000000..09f0b14
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_1.py
@@ -0,0 +1,23 @@
+from calculus.base_1 import Calculation
+from twisted.trial import unittest
+
+class CalculationTestCase(unittest.TestCase):
+ def test_add(self):
+ calc = Calculation()
+ result = calc.add(3, 8)
+ self.assertEqual(result, 11)
+
+ def test_subtract(self):
+ calc = Calculation()
+ result = calc.subtract(7, 3)
+ self.assertEqual(result, 4)
+
+ def test_multiply(self):
+ calc = Calculation()
+ result = calc.multiply(12, 5)
+ self.assertEqual(result, 60)
+
+ def test_divide(self):
+ calc = Calculation()
+ result = calc.divide(12, 5)
+ self.assertEqual(result, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_2.py b/doc/core/howto/listings/trial/calculus/test/test_base_2.py
new file mode 100644
index 0000000..b9bdcb1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_2.py
@@ -0,0 +1,29 @@
+from calculus.base_2 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+
+ def test_add(self):
+ calc = Calculation()
+ result = calc.add(3, 8)
+ self.assertEqual(result, 11)
+
+
+ def test_subtract(self):
+ calc = Calculation()
+ result = calc.subtract(7, 3)
+ self.assertEqual(result, 4)
+
+
+ def test_multiply(self):
+ calc = Calculation()
+ result = calc.multiply(12, 5)
+ self.assertEqual(result, 60)
+
+
+ def test_divide(self):
+ calc = Calculation()
+ result = calc.divide(12, 5)
+ self.assertEqual(result, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_2b.py b/doc/core/howto/listings/trial/calculus/test/test_base_2b.py
new file mode 100644
index 0000000..c05135d
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_2b.py
@@ -0,0 +1,29 @@
+from calculus.base_2 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.calc = Calculation()
+
+
+ def _test(self, operation, a, b, expected):
+ result = operation(a, b)
+ self.assertEqual(result, expected)
+
+
+ def test_add(self):
+ self._test(self.calc.add, 3, 8, 11)
+
+
+ def test_subtract(self):
+ self._test(self.calc.subtract, 7, 3, 4)
+
+
+ def test_multiply(self):
+ self._test(self.calc.multiply, 6, 9, 54)
+
+
+ def test_divide(self):
+ self._test(self.calc.divide, 12, 5, 2)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_base_3.py b/doc/core/howto/listings/trial/calculus/test/test_base_3.py
new file mode 100644
index 0000000..e22541e
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_base_3.py
@@ -0,0 +1,52 @@
+from calculus.base_3 import Calculation
+from twisted.trial import unittest
+
+
+
+class CalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.calc = Calculation()
+
+
+ def _test(self, operation, a, b, expected):
+ result = operation(a, b)
+ self.assertEqual(result, expected)
+
+
+ def _test_error(self, operation):
+ self.assertRaises(TypeError, operation, "foo", 2)
+ self.assertRaises(TypeError, operation, "bar", "egg")
+ self.assertRaises(TypeError, operation, [3], [8, 2])
+ self.assertRaises(TypeError, operation, {"e": 3}, {"r": "t"})
+
+
+ def test_add(self):
+ self._test(self.calc.add, 3, 8, 11)
+
+
+ def test_subtract(self):
+ self._test(self.calc.subtract, 7, 3, 4)
+
+
+ def test_multiply(self):
+ self._test(self.calc.multiply, 6, 9, 54)
+
+
+ def test_divide(self):
+ self._test(self.calc.divide, 12, 5, 2)
+
+
+ def test_errorAdd(self):
+ self._test_error(self.calc.add)
+
+
+ def test_errorSubtract(self):
+ self._test_error(self.calc.subtract)
+
+
+ def test_errorMultiply(self):
+ self._test_error(self.calc.multiply)
+
+
+ def test_errorDivide(self):
+ self._test_error(self.calc.divide)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_1.py b/doc/core/howto/listings/trial/calculus/test/test_client_1.py
new file mode 100644
index 0000000..28c3408
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_1.py
@@ -0,0 +1,37 @@
+from calculus.client_1 import RemoteCalculationClient
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransport()
+ self.proto = RemoteCalculationClient()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_2.py b/doc/core/howto/listings/trial/calculus/test/test_client_2.py
new file mode 100644
index 0000000..45c0a28
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_2.py
@@ -0,0 +1,48 @@
+from calculus.client_2 import RemoteCalculationClient, ClientTimeoutError
+
+from twisted.internet import task
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransportWithDisconnection()
+ self.clock = task.Clock()
+ self.proto = RemoteCalculationClient()
+ self.tr.protocol = self.proto
+ self.proto.callLater = self.clock.callLater
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_timeout(self):
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+ return self.assertFailure(d, ClientTimeoutError)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_client_3.py b/doc/core/howto/listings/trial/calculus/test/test_client_3.py
new file mode 100644
index 0000000..78ac5e1
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_client_3.py
@@ -0,0 +1,63 @@
+from calculus.client_3 import RemoteCalculationClient, ClientTimeoutError
+
+from twisted.internet import task
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class ClientCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tr = proto_helpers.StringTransportWithDisconnection()
+ self.clock = task.Clock()
+ self.proto = RemoteCalculationClient()
+ self.tr.protocol = self.proto
+ self.proto.callLater = self.clock.callLater
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ d = getattr(self.proto, operation)(a, b)
+ self.assertEqual(self.tr.value(), '%s %d %d\r\n' % (operation, a, b))
+ self.tr.clear()
+ d.addCallback(self.assertEqual, expected)
+ self.proto.dataReceived("%d\r\n" % (expected,))
+ return d
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_timeout(self):
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+ return self.assertFailure(d, ClientTimeoutError)
+
+
+ def test_timeoutConnectionLost(self):
+ called = []
+ def lost(arg):
+ called.append(True)
+ self.proto.connectionLost = lost
+
+ d = self.proto.add(9, 4)
+ self.assertEqual(self.tr.value(), 'add 9 4\r\n')
+ self.clock.advance(self.proto.timeOut)
+
+ def check(ignore):
+ self.assertEqual(called, [True])
+ return self.assertFailure(d, ClientTimeoutError).addCallback(check)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_1.py b/doc/core/howto/listings/trial/calculus/test/test_remote_1.py
new file mode 100644
index 0000000..5f41657
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_1.py
@@ -0,0 +1,34 @@
+from calculus.remote_1 import RemoteCalculationFactory
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class RemoteCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.tr = proto_helpers.StringTransport()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ self.proto.dataReceived('%s %d %d\r\n' % (operation, a, b))
+ self.assertEqual(int(self.tr.value()), expected)
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_2.py b/doc/core/howto/listings/trial/calculus/test/test_remote_2.py
new file mode 100644
index 0000000..75b5011
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_2.py
@@ -0,0 +1,46 @@
+from calculus.remote_1 import RemoteCalculationFactory
+from calculus.client_2 import RemoteCalculationClient
+
+from twisted.trial import unittest
+from twisted.internet import reactor, protocol
+
+
+
+class RemoteRunCalculationTestCase(unittest.TestCase):
+
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.port = reactor.listenTCP(0, factory, interface="127.0.0.1")
+ self.client = None
+
+
+ def tearDown(self):
+ if self.client is not None:
+ self.client.transport.loseConnection()
+ return self.port.stopListening()
+
+
+ def _test(self, op, a, b, expected):
+ creator = protocol.ClientCreator(reactor, RemoteCalculationClient)
+ def cb(client):
+ self.client = client
+ return getattr(self.client, op)(a, b
+ ).addCallback(self.assertEqual, expected)
+ return creator.connectTCP('127.0.0.1', self.port.getHost().port
+ ).addCallback(cb)
+
+
+ def test_add(self):
+ return self._test("add", 5, 9, 14)
+
+
+ def test_subtract(self):
+ return self._test("subtract", 47, 13, 34)
+
+
+ def test_multiply(self):
+ return self._test("multiply", 7, 3, 21)
+
+
+ def test_divide(self):
+ return self._test("divide", 84, 10, 8)
diff --git a/doc/core/howto/listings/trial/calculus/test/test_remote_3.py b/doc/core/howto/listings/trial/calculus/test/test_remote_3.py
new file mode 100644
index 0000000..0f0c555
--- /dev/null
+++ b/doc/core/howto/listings/trial/calculus/test/test_remote_3.py
@@ -0,0 +1,40 @@
+from calculus.remote_2 import RemoteCalculationFactory
+from twisted.trial import unittest
+from twisted.test import proto_helpers
+
+
+
+class RemoteCalculationTestCase(unittest.TestCase):
+ def setUp(self):
+ factory = RemoteCalculationFactory()
+ self.proto = factory.buildProtocol(('127.0.0.1', 0))
+ self.tr = proto_helpers.StringTransport()
+ self.proto.makeConnection(self.tr)
+
+
+ def _test(self, operation, a, b, expected):
+ self.proto.dataReceived('%s %d %d\r\n' % (operation, a, b))
+ self.assertEqual(int(self.tr.value()), expected)
+
+
+ def test_add(self):
+ return self._test('add', 7, 6, 13)
+
+
+ def test_subtract(self):
+ return self._test('subtract', 82, 78, 4)
+
+
+ def test_multiply(self):
+ return self._test('multiply', 2, 8, 16)
+
+
+ def test_divide(self):
+ return self._test('divide', 14, 3, 4)
+
+
+ def test_invalidParameters(self):
+ self.proto.dataReceived('add foo bar\r\n')
+ self.assertEqual(self.tr.value(), "error\r\n")
+ errors = self.flushLoggedErrors(TypeError)
+ self.assertEqual(len(errors), 1)
diff --git a/doc/core/howto/listings/udp/MulticastClient.py b/doc/core/howto/listings/udp/MulticastClient.py
new file mode 100644
index 0000000..7a41aaa
--- /dev/null
+++ b/doc/core/howto/listings/udp/MulticastClient.py
@@ -0,0 +1,19 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+
+class MulticastPingClient(DatagramProtocol):
+
+ def startProtocol(self):
+ # Join the multicast address, so we can receive replies:
+ self.transport.joinGroup("228.0.0.5")
+ # Send to 228.0.0.5:8005 - all listeners on the multicast address
+ # (including us) will receive this message.
+ self.transport.write('Client: Ping', ("228.0.0.5", 8005))
+
+ def datagramReceived(self, datagram, address):
+ print "Datagram %s received from %s" % (repr(datagram), repr(address))
+
+
+reactor.listenMulticast(8005, MulticastPingClient(), listenMultiple=True)
+reactor.run()
diff --git a/doc/core/howto/listings/udp/MulticastServer.py b/doc/core/howto/listings/udp/MulticastServer.py
new file mode 100644
index 0000000..0909e60
--- /dev/null
+++ b/doc/core/howto/listings/udp/MulticastServer.py
@@ -0,0 +1,28 @@
+from twisted.internet.protocol import DatagramProtocol
+from twisted.internet import reactor
+
+
+class MulticastPingPong(DatagramProtocol):
+
+ def startProtocol(self):
+ """
+ Called after protocol has started listening.
+ """
+ # Set the TTL>1 so multicast will cross router hops:
+ self.transport.setTTL(5)
+ # Join a specific multicast group:
+ self.transport.joinGroup("228.0.0.5")
+
+ def datagramReceived(self, datagram, address):
+ print "Datagram %s received from %s" % (repr(datagram), repr(address))
+ if datagram == "Client: Ping":
+ # Rather than replying to the group multicast address, we send the
+ # reply directly (unicast) to the originating port:
+ self.transport.write("Server: Pong", address)
+
+
+# We use listenMultiple=True so that we can run MulticastServer.py and
+# MulticastClient.py on same machine:
+reactor.listenMulticast(8005, MulticastPingPong(),
+ listenMultiple=True)
+reactor.run()
diff --git a/doc/core/howto/logging.html b/doc/core/howto/logging.html
new file mode 100644
index 0000000..95f8f0d
--- /dev/null
+++ b/doc/core/howto/logging.html
@@ -0,0 +1,196 @@
+
+
+Twisted Documentation: Logging with twisted.python.log
+
+
+
+
+ Logging with twisted.python.log
+
+
+
+
+
Basic usage
+
+
Twisted provides a simple and flexible logging system in the twisted.python.log
module. It has three commonly used
+ functions:
+
+
+ msg
+ Logs a new message. For example:
+ 1
+2
+
from twisted .python import log
+log .msg ('Hello, world.' )
+
+
+
+ err
+ Writes a failure to the log, including traceback information (if any).
+ You can pass it a Failure
or Exception instance, or
+ nothing. If you pass something else, it will be converted to a string
+ with repr
and logged.
+
+ If you pass nothing, it will construct a Failure from the
+ currently active exception, which makes it convenient to use in an except
clause:
+ 1
+2
+3
+4
+
try :
+ x = 1 / 0
+except :
+ log .err ()
+
+
+
+ startLogging
+ Starts logging to a given file-like object. For example:
+ 1
+
log .startLogging (open ('/var/log/foo.log' , 'w' ))
+
+ or:
+ 1
+
log .startLogging (sys .stdout )
+
+ or:
+ 1
+2
+3
+
from twisted .python .logfile import DailyLogFile
+
+log .startLogging (DailyLogFile .fromFullPath ("/var/log/foo.log" ))
+
+
+ By default, startLogging
will also redirect anything written
+ to sys.stdout
and sys.stderr
to the log. You
+ can disable this by passing setStdout=False
to
+ startLogging
.
+
+
+
+
Before startLogging
is called, log messages will be
+ discarded and errors will be written to stderr.
+
+
Logging and twistd
+
+
If you are using twistd
to run your daemon, it
+ will take care of calling startLogging
for you, and will also
+ rotate log files. See twistd and tac
+ and the twistd
man page for details of using
+ twistd.
+
+
Log files
+
+
The twisted.python.logfile
module provides
+ some standard classes suitable for use with startLogging
, such
+ as DailyLogFile
,
+ which will rotate the log to a new file once per day.
+
+
Using the standard library logging module
+
+
If your application uses the
+ Python standard
+ library logging module or you want to use its easy configuration but
+ don't want to lose twisted-produced messages, the observer
+ PythonLoggingObserver
+ should be useful to you.
+
+
+
You just start it like any other observer:
+
1
+2
+
observer = log .PythonLoggingObserver ()
+observer .start ()
+
+
+ Then
configure the
+ standard library logging module to behave as you want.
+
+
+
This method allows you to customize the log level received by the
+ standard library logging module using the logLevel
keyword:
+
1
+2
+
log .msg ("This is important!" , logLevel =logging .CRITICAL )
+log .msg ("Don't mind" , logLevel =logging .DEBUG )
+
+ Unless
logLevel
is provided, logging.INFO is used for
log.msg
+ and
logging.ERROR
is used for
log.err
.
+
+
+
One special care should be made when you use special configuration of
+ the standard library logging module: some handlers (e.g. SMTP, HTTP) use the network and
+ so can block inside the reactor loop. Nothing in PythonLoggingObserver
is
+ done to prevent that.
+
+
Writing log observers
+
+
Log observers are the basis of the Twisted logging system.
+ Whenever log.msg
(or log.err
) is called, an
+ event is emitted. The event is passed to each observer which has been
+ registered. There can be any number of observers, and each can treat
+ the event in any way desired.
+ An example of
+ a log observer in Twisted is the emit
method of FileLogObserver
.
+ FileLogObserver
, used by
+ startLogging
, writes events to a log file. A log observer
+ is just a callable that accepts a dictionary as its only argument. You can
+ then register it to receive all log events (in addition to any other
+ observers):
+
+
1
+
twisted .python .log .addObserver (yourCallable )
+
+
+
The dictionary will have at least two items:
+
+
+ message
+ The message (a list, usually of strings)
+ for this log event, as passed to log.msg
or the
+ message in the failure passed to log.err
.
+
+ isError
+ This is a boolean that will be true if this event came from a call to
+ log.err
. If this is set, there may be a failure
+ item in the dictionary as will, with a Failure object in it.
+
+
+
Other items the built in logging functionality may add include:
+
+
+ printed
+ This message was captured from sys.stdout
, i.e. this
+ message came from a print
statement. If
+ isError
is also true, it came from
+ sys.stderr
.
+
+
+
You can pass additional items to the event dictionary by passing keyword
+ arguments to log.msg
and log.err
. The standard
+ log observers will ignore dictionary items they don't use.
+
+
Important notes:
+
+
+ Never block in a log observer, as it may run in main Twisted thread.
+ This means you can't use socket or syslog standard library logging backends.
+
+ The observer needs to be thread safe if you anticipate using threads
+ in your program.
+
+
+
Customizing twistd
logging
+
+ The behavior of the logging that twistd
does can be
+ customized either with the --logger
option or by setting the
+ ILogObserver
component on the application object. See the Application document for more information.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/options.html b/doc/core/howto/options.html
new file mode 100644
index 0000000..f674ad2
--- /dev/null
+++ b/doc/core/howto/options.html
@@ -0,0 +1,581 @@
+
+
+Twisted Documentation: Parsing command-lines with usage.Options
+
+
+
+
+ Parsing command-lines with usage.Options
+
+
+
+
+
Introduction
+
+
There is frequently a need for programs to parse a UNIX-like
+ command line program: options preceded by -
or
+ --
, sometimes followed by a parameter, followed by
+ a list of arguments. The twisted.python.usage
provides a class,
+ Options
, to facilitate such parsing.
+
+
While Python has the getopt
module for doing
+ this, it provides a very low level of abstraction for options.
+ Twisted has a higher level of abstraction, in the class twisted.python.usage.Options
. It uses
+ Python's reflection facilities to provide an easy to use yet
+ flexible interface to the command line. While most command line
+ processors either force the application writer to write her own
+ loops, or have arbitrary limitations on the command line (the
+ most common one being not being able to have more then one
+ instance of a specific option, thus rendering the idiom
+ program -v -v -v
impossible), Twisted allows the
+ programmer to decide how much control she wants.
+
+
The Options
class is used by subclassing. Since
+ a lot of time it will be used in the twisted.tap
package, where the local
+ conventions require the specific options parsing class to also
+ be called Options
, it is usually imported with
+
1
+
from twisted .python import usage
+
+
+
Boolean Options
+
+
For simple boolean options, define the attribute
+ optFlags
like this:
+
1
+2
+3
+
class Options (usage .Options ):
+
+ optFlags = [["fast" , "f" , "Act quickly" ], ["safe" , "s" , "Act safely" ]]
+
+
optFlags
should be a list of 3-lists. The first element
+ is the long name, and will be used on the command line as
+ --fast
. The second one is the short name, and will be used
+ on the command line as -f
. The last element is a
+ description of the flag and will be used to generate the usage
+ information text. The long name also determines the name of the key
+ that will be set on the Options instance. Its value will be 1 if the
+ option was seen, 0 otherwise. Here is an example for usage:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
class Options (usage .Options ):
+
+ optFlags = [
+ ["fast" , "f" , "Act quickly" ],
+ ["good" , "g" , "Act well" ],
+ ["cheap" , "c" , "Act cheaply" ]
+ ]
+
+command_line = ["-g" , "--fast" ]
+
+options = Options ()
+try :
+ options .parseOptions (command_line )
+except usage .UsageError , errortext :
+ print '%s: %s' % (sys .argv [0 ], errortext )
+ print '%s: Try --help for usage details.' % (sys .argv [0 ])
+ sys .exit (1 )
+if options ['fast' ]:
+ print "fast" ,
+if options ['good' ]:
+ print "good" ,
+if options ['cheap' ]:
+ print "cheap" ,
+print
+
+
+
The above will print fast good
.
+
+
Note here that Options fully supports the mapping interface. You can
+ access it mostly just like you can access any other dict. Options are stored
+ as mapping items in the Options instance: parameters as 'paramname': 'value'
+ and flags as 'flagname': 1 or 0.
+
+
Inheritance, Or: How I Learned to Stop Worrying and Love
+ the Superclass
+
+
Sometimes there is a need for several option processors with
+ a unifying core. Perhaps you want all your commands to
+ understand -q
/--quiet
means to be
+ quiet, or something similar. On the face of it, this looks
+ impossible: in Python, the subclass's optFlags
+ would shadow the superclass's. However,
+ usage.Options
uses special reflection code to get
+ all of the optFlags
defined in the hierarchy. So
+ the following:
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
class BaseOptions (usage .Options ):
+
+ optFlags = [["quiet" , "q" , None ]]
+
+class SpecificOptions (BaseOptions ):
+
+ optFlags = [
+ ["fast" , "f" , None ], ["good" , "g" , None ], ["cheap" , "c" , None ]
+ ]
+
+
Is the same as:
+
1
+2
+3
+4
+5
+6
+7
+8
+
class SpecificOptions (BaseOptions ):
+
+ optFlags = [
+ ["quiet" , "q" , "Silence output" ],
+ ["fast" , "f" , "Run quickly" ],
+ ["good" , "g" , "Don't validate input" ],
+ ["cheap" , "c" , "Use cheap resources" ]
+ ]
+
+
+
Parameters
+
+
Parameters are specified using the attribute
+ optParameters
. They must be given a
+ default. If you want to make sure you got the parameter from
+ the command line, give a non-string default. Since the command
+ line only has strings, this is completely reliable.
+
+
Here is an example:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
from twisted .python import usage
+
+class Options (usage .Options ):
+
+ optFlags = [
+ ["fast" , "f" , "Run quickly" ],
+ ["good" , "g" , "Don't validate input" ],
+ ["cheap" , "c" , "Use cheap resources" ]
+ ]
+ optParameters = [["user" , "u" , None , "The user name" ]]
+
+config = Options ()
+try :
+ config .parseOptions ()
+except usage .UsageError , errortext :
+ print '%s: %s' % (sys .argv [0 ], errortext )
+ print '%s: Try --help for usage details.' % (sys .argv [0 ])
+ sys .exit (1 )
+
+if config ['user' ] is not None :
+ print "Hello" , config ['user' ]
+print "So, you want it:"
+
+if config ['fast' ]:
+ print "fast" ,
+if config ['good' ]:
+ print "good" ,
+if config ['cheap' ]:
+ print "cheap" ,
+print
+
+
+
Like optFlags
, optParameters
works
+ smoothly with inheritance.
+
+
Option Subcommands
+
+
It is useful, on occassion, to group a set of options together based
+ on the logical action to which they belong. For this, the
+ usage.Options
class allows you to define a set of
+ subcommands , each of which can provide its own
+ usage.Options
instance to handle its particular
+ options.
+
+
Here is an example for an Options class that might parse
+ options like those the cvs program takes
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
from twisted .python import usage
+
+class ImportOptions (usage .Options ):
+ optParameters = [
+ ['module' , 'm' , None , None ], ['vendor' , 'v' , None , None ],
+ ['release' , 'r' , None ]
+ ]
+
+class CheckoutOptions (usage .Options ):
+ optParameters = [['module' , 'm' , None , None ], ['tag' , 'r' , None , None ]]
+
+class Options (usage .Options ):
+ subCommands = [['import' , None , ImportOptions , "Do an Import" ],
+ ['checkout' , None , CheckoutOptions , "Do a Checkout" ]]
+
+ optParameters = [
+ ['compression' , 'z' , 0 , 'Use compression' ],
+ ['repository' , 'r' , None , 'Specify an alternate repository' ]
+ ]
+
+config = Options (); config .parseOptions ()
+if config .subCommand == 'import' :
+ doImport (config .subOptions )
+elif config .subCommand == 'checkout' :
+ doCheckout (config .subOptions )
+
+
+
The subCommands
attribute of Options
+ directs the parser to the two other Options
subclasses
+ when the strings "import"
or "checkout"
are
+ present on the command
+ line. All options after the given command string are passed to the
+ specified Options subclass for further parsing. Only one subcommand
+ may be specified at a time. After parsing has completed, the Options
+ instance has two new attributes - subCommand
and
+ subOptions
- which hold the command string and the Options
+ instance used to parse the remaining options.
+
+
Generic Code For Options
+
+
Sometimes, just setting an attribute on the basis of the
+ options is not flexible enough. In those cases, Twisted does
+ not even attempt to provide abstractions such as counts or
+ lists , but rathers lets you call your own method, which will
+ be called whenever the option is encountered.
+
+
Here is an example of counting verbosity
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
from twisted .python import usage
+
+class Options (usage .Options ):
+
+ def __init__ (self ):
+ usage .Options .__init__ (self )
+ self ['verbosity' ] = 0
+
+ def opt_verbose (self ):
+ self ['verbosity' ] = self ['verbosity' ]+1
+
+ def opt_quiet (self ):
+ self ['verbosity' ] = self ['verbosity' ]-1
+
+ opt_v = opt_verbose
+ opt_q = opt_quiet
+
+
+
Command lines that look like
+ command -v -v -v -v
will
+ increase verbosity to 4, while
+ command -q -q -q
will decrease
+ verbosity to -3.
+
+
+
The usage.Options
+ class knows that these are
+ parameter-less options, since the methods do not receive an
+ argument. Here is an example for a method with a parameter:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
from twisted .python import usage
+
+class Options (usage .Options ):
+
+ def __init__ (self ):
+ usage .Options .__init__ (self )
+ self ['symbols' ] = []
+
+ def opt_define (self , symbol ):
+ self ['symbols' ].append (symbol )
+
+ opt_D = opt_define
+
+
+
This example is useful for the common idiom of having
+ command -DFOO -DBAR
to define symbols.
+
+
Parsing Arguments
+
+
usage.Options
does not stop helping when the
+ last parameter is gone. All the other arguments are sent into a
+ function which should deal with them. Here is an example for a
+ cmp
like command.
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
from twisted .python import usage
+
+class Options (usage .Options ):
+
+ optParameters = [["max_differences" , "d" , 1 , None ]]
+
+ def parseArgs (self , origin , changed ):
+ self ['origin' ] = origin
+ self ['changed' ] = changed
+
+
+
The command should look like command origin
+ changed
.
+
+
If you want to have a variable number of left-over
+ arguments, just use def parseArgs(self, *args):
.
+ This is useful for commands like the UNIX
+ cat(1)
.
+
+
Post Processing
+
+
Sometimes, you want to perform post processing of options to
+ patch up inconsistencies, and the like. Here is an example:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
from twisted .python import usage
+
+class Options (usage .Options ):
+
+ optFlags = [
+ ["fast" , "f" , "Run quickly" ],
+ ["good" , "g" , "Don't validate input" ],
+ ["cheap" , "c" , "Use cheap resources" ]
+ ]
+
+ def postOptions (self ):
+ if self ['fast' ] and self ['good' ] and self ['cheap' ]:
+ raise usage .UsageError , "can't have it all, brother"
+
+
+
Type enforcement
+
+
By default, all options are handled as strings. You may want to
+ enforce the type of your option in some specific case, the classic example
+ being port number. Any callable can be specified in the fifth row of
+ optParameters
and will be called with the string value passed
+ in parameter.
+
+
+
1
+2
+3
+4
+5
+6
+7
+
from twisted .python import usage
+
+class Options (usage .Options ):
+ optParameters = [
+ ["shiny_integer" , "s" , 1 , None , int ],
+ ["dummy_float" , "d" , 3.14159 , None , float ],
+ ]
+
+
+
Note that default values are not coerced, so you should either declare
+ it with the good type (as above) or handle it when you use your
+ options.
+
+
The coerce function may have a coerceDoc attribute, the content of which
+ will be printed after the documentation of the option. It's particularly
+ useful for reusing the function at multiple places.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
def oneTwoThree (val ):
+ val = int (val )
+ if val not in range (1 , 4 ):
+ raise ValueError ("Not in range" )
+ return val
+oneTwoThree .coerceDoc = "Must be 1, 2 or 3."
+
+from twisted .python import usage
+
+class Options (usage .Options ):
+ optParameters = [["one_choice" , "o" , 1 , None , oneTwoThree ]]
+
+
+
This example code will print the following help when added to your program:
+
+
+
+$ python myprogram.py --help
+Usage: myprogram [options]
+Options:
+ -o, --one_choice= [default: 0]. Must be 1, 2 or 3.
+
+
Shell tab-completion
+
+
The Options
class may provide tab-completion to interactive
+ command shells. Only zsh
is supported at present, but there is
+ some interest in supporting bash
in the future.
+
+
Support is automatic for all of the commands shipped with Twisted. Zsh
+ has shipped, for a number of years, a completion function which ties in to
+ the support provided by the Options
class.
+
+
If you are writing a twistd
plugin, then tab-completion
+ for your twistd
sub-command is also automatic.
+
+
For other commands you may easily provide zsh tab-completion support.
+ Copy the file "twisted/python/twisted-completion.zsh" and name it something
+ like "_mycommand". A leading underscore with no extension is zsh's
+ convention for completion function files.
+
+
Edit the new file and change the first line to refer only to your new
+ command(s), like so:
+
+
+#compdef mycommand
+
+
+
Then ensure this file is made available to the shell by placing it in
+ one of the directories appearing in zsh's $fpath. Restart zsh, and ensure
+ advanced completion is enabled
+ (autoload -U compinit; compinit)
. You should then be able to
+ type the name of your command and press Tab to have your command-line
+ options completed.
+
+
Completion metadata
+
+
Optionally, a special attribute, compData
, may be defined
+ on your Options
subclass in order to provide more information
+ to the shell-completion system. The attribute should be an instance of
+ Completions . See that class
+ for further details.
+
+
In addition, compData
may be defined on parent classes in
+ your inheritance hiearchy. The information from each
+ Completions instance will be
+ aggregated when producing the final tab-completion results.
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-clients.html b/doc/core/howto/pb-clients.html
new file mode 100644
index 0000000..ae5445e
--- /dev/null
+++ b/doc/core/howto/pb-clients.html
@@ -0,0 +1,362 @@
+
+
+Twisted Documentation: Managing Clients of Perspectives
+
+
+
+
+ Managing Clients of Perspectives
+
+
+
+
+
Overview
+
+
In all the IPerspective
uses
+we have shown so far, we ignored the mind
argument and created
+a new Avatar
for every connection. This is usually an easy
+design choice, and it works well for simple cases.
+
+
In more complicated cases, for example an Avatar
that
+represents a player object which is persistent in the game universe,
+we will want connections from the same player to use the same
+Avatar
.
+
+
Another thing which is necessary in more complicated scenarios
+is notifying a player asynchronously. While it is possible, of
+course, to allow a player to call
+perspective_remoteListener(referencable)
that would
+mean both duplication of code and a higher latency in logging in,
+both bad.
+
+
In previous sections all realms looked to be identical.
+In this one we will show the usefulness of realms in accomplishing
+those two objectives.
+
+
Managing Avatars
+
+
The simplest way to manage persistent avatars is to use a straight-forward
+caching mechanism:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
from zope .interface import implements
+
+class SimpleAvatar (pb .Avatar ):
+ greetings = 0
+ def __init__ (self , name ):
+ self .name = name
+ def perspective_greet (self ):
+ self .greetings += 1
+ return "<%d>hello %s" % (self .greetings , self .name )
+
+class CachingRealm :
+ implements (portal .IRealm )
+
+ def __init__ (self ):
+ self .avatars = {}
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces : raise NotImplementedError
+ if avatarId in self .avatars :
+ p = self .avatars [avatarId ]
+ else :
+ p = self .avatars [avatarId ] = SimpleAvatar (avatarId )
+ return pb .IPerspective , p , lambda :None
+
+
+
This gives us a perspective which counts the number of greetings it
+sent its client. Implementing a caching strategy, as opposed to generating
+a realm with the correct avatars already in it, is usually easier. This
+makes adding new checkers to the portal, or adding new users to a checker
+database, transparent. Otherwise, careful synchronization is needed between
+the checker and avatar is needed (much like the synchronization between
+UNIX's /etc/shadow
and /etc/passwd
).
+
+
Sometimes, however, an avatar will need enough per-connection state
+that it would be easier to generate a new avatar and cache something
+else. Here is an example of that:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
from zope .interface import implements
+
+class Greeter :
+ greetings = 0
+ def hello (self ):
+ self .greetings += 1
+ return "<%d>hello" % (self .greetings , self .name )
+
+class SimpleAvatar (pb .Avatar ):
+ def __init__ (self , name , greeter ):
+ self .name = name
+ self .greeter = greeter
+ def perspective_greet (self ):
+ return self .greeter .hello ()+' ' +self .name
+
+class CachingRealm :
+ implements (portal .IRealm )
+
+ def __init__ (self ):
+ self .greeters = {}
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces : raise NotImplementedError
+ if avatarId in self .greeters :
+ p = self .greeters [avatarId ]
+ else :
+ p = self .greeters [avatarId ] = Greeter ()
+ return pb .IPerspective , SimpleAvatar (avatarId , p ), lambda :None
+
+
+
It might seem tempting to use this pattern to have an avatar which
+is notified of new connections. However, the problems here are twofold:
+it would lead to a thin class which needs to forward all of its methods,
+and it would be impossible to know when disconnections occur. Luckily,
+there is a better pattern:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
from zope .interface import implements
+
+class SimpleAvatar (pb .Avatar ):
+ greetings = 0
+ connections = 0
+ def __init__ (self , name ):
+ self .name = name
+ def connect (self ):
+ self .connections += 1
+ def disconnect (self ):
+ self .connections -= 1
+ def perspective_greet (self ):
+ self .greetings += 1
+ return "<%d>hello %s" % (self .greetings , self .name )
+
+class CachingRealm :
+ implements (portal .IRealm )
+
+ def __init__ (self ):
+ self .avatars = {}
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces : raise NotImplementedError
+ if avatarId in self .avatars :
+ p = self .avatars [avatarId ]
+ else :
+ p = self .avatars [avatarId ] = SimpleAvatar (avatarId )
+ p .connect ()
+ return pb .IPerspective , p , p .disconnect
+
+
+
It is possible to use such a pattern to define an arbitrary limit for
+the number of concurrent connections:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from zope .interface import implements
+
+class SimpleAvatar (pb .Avatar ):
+ greetings = 0
+ connections = 0
+ def __init__ (self , name ):
+ self .name = name
+ def connect (self ):
+ self .connections += 1
+ def disconnect (self ):
+ self .connections -= 1
+ def perspective_greet (self ):
+ self .greetings += 1
+ return "<%d>hello %s" % (self .greetings , self .name )
+
+class CachingRealm :
+ implements (portal .IRealm )
+
+ def __init__ (self , max =1 ):
+ self .avatars = {}
+ self .max = max
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces : raise NotImplementedError
+ if avatarId in self .avatars :
+ p = self .avatars [avatarId ]
+ else :
+ p = self .avatars [avatarId ] = SimpleAvatar (avatarId )
+ if p .connections >= self .max :
+ raise ValueError ("too many connections" )
+ p .connect ()
+ return pb .IPerspective , p , p .disconnect
+
+
+
Managing Clients
+
+
So far, all our realms have ignored the mind
argument.
+In the case of PB, the mind
is an object supplied by
+the remote login method -- usually, when it passes over the wire,
+it becomes a pb.RemoteReference
. This object allows
+sending messages to the client as soon as the connection is established
+and authenticated.
+
+
Here is a simple remote-clock application which shows the usefulness
+of the mind
argument:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
from zope .interface import implements
+
+class SimpleAvatar (pb .Avatar ):
+ def __init__ (self , client ):
+ self .s = internet .TimerService (1 , self .telltime )
+ self .s .startService ()
+ self .client = client
+ def telltime (self ):
+ self .client .callRemote ("notifyTime" , time .time ())
+ def perspective_setperiod (self , period ):
+ self .s .stopService ()
+ self .s = internet .TimerService (period , self .telltime )
+ self .s .startService ()
+ def logout (self ):
+ self .s .stopService ()
+
+class Realm :
+ implements (portal .IRealm )
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces : raise NotImplementedError
+ p = SimpleAvatar (mind )
+ return pb .IPerspective , p , p .logout
+
+
+
In more complicated situations, you might want to cache the avatars
+and give each one a set of current clients or something similar.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-copyable.html b/doc/core/howto/pb-copyable.html
new file mode 100644
index 0000000..3e3f0e4
--- /dev/null
+++ b/doc/core/howto/pb-copyable.html
@@ -0,0 +1,1185 @@
+
+
+Twisted Documentation: PB Copyable: Passing Complex Types
+
+
+
+
+ PB Copyable: Passing Complex Types
+
+
+
+
+
Overview
+
+
This chapter focuses on how to use PB to pass complex types (specifically
+class instances) to and from a remote process. The first section is on
+simply copying the contents of an object to a remote process (pb.Copyable
). The second covers how
+to copy those contents once, then update them later when they change (Cacheable
).
+
+
Motivation
+
+
From the previous chapter , you've seen how to
+pass basic types to a remote process, by using them in the arguments or
+return values of a callRemote
function. However,
+if you've experimented with it, you may have discovered problems when trying
+to pass anything more complicated than a primitive int/list/dict/string
+type, or another pb.Referenceable
object. At some point you want
+to pass entire objects between processes, instead of having to reduce them
+down to dictionaries on one end and then re-instantiating them on the
+other.
+
+
Passing Objects
+
+
The most obvious and straightforward way to send an object to a remote
+process is with something like the following code. It also happens that this
+code doesn't work, as will be explained below.
+
+
1
+2
+3
+4
+5
+6
+
class LilyPond :
+ def __init__ (self , frogs ):
+ self .frogs = frogs
+
+pond = LilyPond (12 )
+ref .callRemote ("sendPond" , pond )
+
+
+
If you try to run this, you might hope that a suitable remote end which
+implements the remote_sendPond
method would see that method get
+invoked with an instance from the LilyPond
class. But instead,
+you'll encounter the dreaded InsecureJelly
exception. This is
+Twisted's way of telling you that you've violated a security restriction,
+and that the receiving end refuses to accept your object.
+
+
Security Options
+
+
What's the big deal? What's wrong with just copying a class into another
+process' namespace?
+
+
Reversing the question might make it easier to see the issue: what is the
+problem with accepting a stranger's request to create an arbitrary object in
+your local namespace? The real question is how much power you are granting
+them: what actions can they convince you to take on the basis of the bytes
+they are sending you over that remote connection.
+
+
Objects generally represent more power than basic types like strings and
+dictionaries because they also contain (or reference) code, which can modify
+other data structures when executed. Once previously-trusted data is
+subverted, the rest of the program is compromised.
+
+
The built-in Python batteries included classes are relatively
+tame, but you still wouldn't want to let a foreign program use them to
+create arbitrary objects in your namespace or on your computer. Imagine a
+protocol that involved sending a file-like object with a read()
+method that was supposed to used later to retrieve a document. Then imagine
+what if that object were created with
+ os.fdopen("~/.gnupg/secring.gpg")
. Or an instance of
+ telnetlib.Telnet("localhost", "chargen")
.
+
+
Classes you've written for your own program are likely to have far more
+power. They may run code during __init__
, or even have special
+meaning simply because of their existence. A program might have
+ User
objects to represent user accounts, and have a rule that
+says all User
objects in the system are referenced when
+authorizing a login session. (In this system, User.__init__
+would probably add the object to a global list of known users). The simple
+act of creating an object would give access to somebody. If you could be
+tricked into creating a bad object, an unauthorized user would get
+access.
+
+
So object creation needs to be part of a system's security design. The
+dotted line between trusted inside and untrusted outside needs
+to describe what may be done in response to outside events. One of those
+events is the receipt of an object through a PB remote procedure call, which
+is a request to create an object in your inside namespace. The
+question is what to do in response to it. For this reason, you must
+explicitly specify what remote classes will be accepted, and how their
+local representatives are to be created.
+
+
What class to use?
+
+
Another basic question to answer before we can do anything useful with an
+incoming serialized object is: what class should we create? The simplistic
+answer is to create the same kind that was serialized on the sender's
+end of the wire, but this is not as easy or as straightforward as you might
+think. Remember that the request is coming from a different program, using a
+potentially different set of class libraries. In fact, since PB has also
+been implemented in Java, Emacs-Lisp, and other languages, there's no
+guarantee that the sender is even running Python! All we know on the
+receiving end is a list of two things which describe the instance they are
+trying to send us: the name of the class, and a representation of the
+contents of the object.
+
+
+
PB lets you specify the mapping from remote class names to local classes
+with the setUnjellyableForClass
function
+1 .
+
+
+This function takes a remote/sender class reference (either the
+fully-qualified name as used by the sending end, or a class object from
+which the name can be extracted), and a local/recipient class (used to
+create the local representation for incoming serialized objects). Whenever
+the remote end sends an object, the class name that they transmit is looked
+up in the table controlled by this function. If a matching class is found,
+it is used to create the local object. If not, you get the
+ InsecureJelly
exception.
+
+
In general you expect both ends to share the same codebase: either you
+control the program that is running on both ends of the wire, or both
+programs share some kind of common language that is implemented in code
+which exists on both ends. You wouldn't expect them to send you an object of
+the MyFooziWhatZit class unless you also had a definition for that class. So
+it is reasonable for the Jelly layer to reject all incoming classes except
+the ones that you have explicitly marked with
+ setUnjellyableForClass
. But keep in mind that the sender's idea
+of a User
object might differ from the recipient's, either
+through namespace collisions between unrelated packages, version skew
+between nodes that haven't been updated at the same rate, or a malicious
+intruder trying to cause your code to fail in some interesting or
+potentially vulnerable way.
+
+
+
pb.Copyable
+
+
Ok, enough of this theory. How do you send a fully-fledged object from
+one side to the other?
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+
+
+
+
+
+from twisted .spread import pb , jelly
+from twisted .python import log
+from twisted .internet import reactor
+
+class LilyPond :
+ def setStuff (self , color , numFrogs ):
+ self .color = color
+ self .numFrogs = numFrogs
+ def countFrogs (self ):
+ print "%d frogs" % self .numFrogs
+
+class CopyPond (LilyPond , pb .Copyable ):
+ pass
+
+class Sender :
+ def __init__ (self , pond ):
+ self .pond = pond
+
+ def got_obj (self , remote ):
+ self .remote = remote
+ d = remote .callRemote ("takePond" , self .pond )
+ d .addCallback (self .ok ).addErrback (self .notOk )
+
+ def ok (self , response ):
+ print "pond arrived" , response
+ reactor .stop ()
+ def notOk (self , failure ):
+ print "error during takePond:"
+ if failure .type == jelly .InsecureJelly :
+ print " InsecureJelly"
+ else :
+ print failure
+ reactor .stop ()
+ return None
+
+def main ():
+ from copy_sender import CopyPond
+ pond = CopyPond ()
+ pond .setStuff ("green" , 7 )
+ pond .countFrogs ()
+
+ print "." .join ([pond .__class__ .__module__ , pond .__class__ .__name__ ])
+
+ sender = Sender (pond )
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ deferred = factory .getRootObject ()
+ deferred .addCallback (sender .got_obj )
+ reactor .run ()
+
+if __name__ == '__main__' :
+ main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+
+
+
+"""
+PB copy receiver example.
+
+This is a Twisted Application Configuration (tac) file. Run with e.g.
+ twistd -ny copy_receiver.tac
+
+See the twistd(1) man page or
+http://twistedmatrix.com/documents/current/howto/application for details.
+"""
+
+import sys
+if __name__ == '__main__' :
+ print __doc__
+ sys .exit (1 )
+
+from twisted .application import service , internet
+from twisted .internet import reactor
+from twisted .spread import pb
+from copy_sender import LilyPond , CopyPond
+
+from twisted .python import log
+
+
+class ReceiverPond (pb .RemoteCopy , LilyPond ):
+ pass
+pb .setUnjellyableForClass (CopyPond , ReceiverPond )
+
+class Receiver (pb .Root ):
+ def remote_takePond (self , pond ):
+ print " got pond:" , pond
+ pond .countFrogs ()
+ return "safe and sound"
+ def remote_shutdown (self ):
+ reactor .stop ()
+
+application = service .Application ("copy_receiver" )
+internet .TCPServer (8800 , pb .PBServerFactory (Receiver ())).setServiceParent (
+ service .IServiceCollection (application ))
+
+
+
The sending side has a class called LilyPond
. To make this
+eligble for transport through callRemote
(either as an
+argument, a return value, or something referenced by either of those [like a
+dictionary value]), it must inherit from one of the four Serializable
classes. In this section,
+we focus on Copyable
.
+The copyable subclass of LilyPond
is called
+ CopyPond
. We create an instance of it and send it through
+ callRemote
as an argument to the receiver's
+ remote_takePond
method. The Jelly layer will serialize
+(jelly ) that object as an instance with a class name of
+copy_sender.CopyPond and some chunk of data that represents the
+object's state. pond.__class__.__module__
and
+ pond.__class__.__name__
are used to derive the class name
+string. The object's getStateToCopy
method is
+used to get the state: this is provided by pb.Copyable
, and the default just retrieves
+ self.__dict__
. This works just like the optional
+ __getstate__
method used by pickle
. The pair of
+name and state are sent over the wire to the receiver.
+
+
The receiving end defines a local class named ReceiverPond
+to represent incoming LilyPond
instances. This class derives
+from the sender's LilyPond
class (with a fully-qualified name
+of copy_sender.LilyPond
), which specifies how we expect it to
+behave. We trust that this is the same LilyPond
class as the
+sender used. (At the very least, we hope ours will be able to accept a state
+created by theirs). It also inherits from pb.RemoteCopy
, which is a requirement for all
+classes that act in this local-representative role (those which are given to
+the second argument of setUnjellyableForClass
).
+ RemoteCopy
provides the methods that tell the Jelly layer how
+to create the local object from the incoming serialized state.
+
+
Then setUnjellyableForClass
is used to register the two
+classes. This has two effects: instances of the remote class (the first
+argument) will be allowed in through the security layer, and instances of
+the local class (the second argument) will be used to contain the state that
+is transmitted when the sender serializes the remote object.
+
+
When the receiver unserializes (unjellies ) the object, it will
+create an instance of the local ReceiverPond
class, and hand
+the transmitted state (usually in the form of a dictionary) to that object's
+ setCopyableState
method.
+This acts just like the __setstate__
method that
+ pickle
uses when unserializing an object.
+ getStateToCopy
/setCopyableState
are distinct from
+ __getstate__
/__setstate__
to allow objects to be
+persisted (across time) differently than they are transmitted (across
+[memory]space).
+
+
When this is run, it produces the following output:
+
+
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory <twisted.spread.pb.PBServerFactory instance at
+0x406159cc>
+[Broker,0,127.0.0.1] got pond: <__builtin__.ReceiverPond instance at
+0x406ec5ec>
+[Broker,0,127.0.0.1] 7 frogs
+
+
+
+$ ./copy_sender.py
+7 frogs
+copy_sender.CopyPond
+pond arrived safe and sound
+Main loop terminated.
+$
+
+
+
+
+
Controlling the Copied State
+
+
By overriding getStateToCopy
and
+ setCopyableState
, you can control how the object is transmitted
+over the wire. For example, you might want perform some data-reduction:
+pre-compute some results instead of sending all the raw data over the wire.
+Or you could replace references to a local object on the sender's side with
+markers before sending, then upon receipt replace those markers with
+references to a receiver-side proxy that could perform the same operations
+against a local cache of data.
+
+
Another good use for getStateToCopy
is to implement
+local-only attributes: data that is only accessible by the local
+process, not to any remote users. For example, a .password
+attribute could be removed from the object state before sending to a remote
+system. Combined with the fact that Copyable
objects return
+unchanged from a round trip, this could be used to build a
+challenge-response system (in fact PB does this with
+ pb.Referenceable
objects to implement authorization as
+described here ).
+
+
Whatever getStateToCopy
returns from the sending object will
+be serialized and sent over the wire; setCopyableState
gets
+whatever comes over the wire and is responsible for setting up the state of
+the object it lives in.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+
+
+
+
+from twisted .spread import pb
+
+class FrogPond :
+ def __init__ (self , numFrogs , numToads ):
+ self .numFrogs = numFrogs
+ self .numToads = numToads
+ def count (self ):
+ return self .numFrogs + self .numToads
+
+class SenderPond (FrogPond , pb .Copyable ):
+ def getStateToCopy (self ):
+ d = self .__dict__ .copy ()
+ d ['frogsAndToads' ] = d ['numFrogs' ] + d ['numToads' ]
+ del d ['numFrogs' ]
+ del d ['numToads' ]
+ return d
+
+class ReceiverPond (pb .RemoteCopy ):
+ def setCopyableState (self , state ):
+ self .__dict__ = state
+ def count (self ):
+ return self .frogsAndToads
+
+pb .setUnjellyableForClass (SenderPond , ReceiverPond )
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+
+
+
+
+
+from twisted .spread import pb , jelly
+from twisted .python import log
+from twisted .internet import reactor
+from copy2_classes import SenderPond
+
+class Sender :
+ def __init__ (self , pond ):
+ self .pond = pond
+
+ def got_obj (self , obj ):
+ d = obj .callRemote ("takePond" , self .pond )
+ d .addCallback (self .ok ).addErrback (self .notOk )
+
+ def ok (self , response ):
+ print "pond arrived" , response
+ reactor .stop ()
+ def notOk (self , failure ):
+ print "error during takePond:"
+ if failure .type == jelly .InsecureJelly :
+ print " InsecureJelly"
+ else :
+ print failure
+ reactor .stop ()
+ return None
+
+def main ():
+ pond = SenderPond (3 , 4 )
+ print "count %d" % pond .count ()
+
+ sender = Sender (pond )
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ deferred = factory .getRootObject ()
+ deferred .addCallback (sender .got_obj )
+ reactor .run ()
+
+if __name__ == '__main__' :
+ main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
+
+
+
+
+from twisted .application import service , internet
+from twisted .internet import reactor
+from twisted .spread import pb
+import copy2_classes
+
+class Receiver (pb .Root ):
+ def remote_takePond (self , pond ):
+ print " got pond:" , pond
+ print " count %d" % pond .count ()
+ return "safe and sound"
+ def remote_shutdown (self ):
+ reactor .stop ()
+
+application = service .Application ("copy_receiver" )
+internet .TCPServer (8800 , pb .PBServerFactory (Receiver ())).setServiceParent (
+ service .IServiceCollection (application ))
+
+
+
In this example, the classes are defined in a separate source file, which
+also sets up the binding between them. The SenderPond
and
+ReceiverPond
are unrelated save for this binding: they happen
+to implement the same methods, but use different internal instance variables
+to accomplish them.
+
+
The recipient of the object doesn't even have to import the class
+definition into their namespace. It is sufficient that they import the class
+definition (and thus execute the setUnjellyableForClass
+statement). The Jelly layer remembers the class definition until a matching
+object is received. The sender of the object needs the definition, of
+course, to create the object in the first place.
+
+
When run, the copy2
example emits the following:
+
+
+$ twistd -n -y copy2_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory <twisted.spread.pb.PBServerFactory instance at
+0x40604b4c>
+[Broker,0,127.0.0.1] got pond: <copy2_classes.ReceiverPond instance at
+0x406eb2ac>
+[Broker,0,127.0.0.1] count 7
+
+
+
+$ ./copy2_sender.py
+count 7
+pond arrived safe and sound
+Main loop terminated.
+
+
+
+
+
Things To Watch Out For
+
+
+
+ The first argument to setUnjellyableForClass
must refer
+ to the class as known by the sender . The sender has no way of
+ knowing about how your local import
statements are set up,
+ and Python's flexible namespace semantics allow you to access the same
+ class through a variety of different names. You must match whatever the
+ sender does. Having both ends import the class from a separate file, using
+ a canonical module name (no sibiling imports ), is a good way to get
+ this right, especially when both the sending and the receiving classes are
+ defined together, with the setUnjellyableForClass
immediately
+ following them.
+
+ The class that is sent must inherit from pb.Copyable
. The class that is registered to
+ receive it must inherit from pb.RemoteCopy
2 .
+
+ The same class can be used to send and receive. Just have it inherit
+ from both pb.Copyable
and pb.RemoteCopy
. This
+ will also make it possible to send the same class symmetrically back and
+ forth over the wire. But don't get confused about when it is coming (and
+ using setCopyableState
) versus when it is going (using
+ getStateToCopy
).
+
+ InsecureJelly
+ exceptions are raised by the receiving end. They will be delivered
+ asynchronously to an errback
handler. If you do not add one
+ to the Deferred
returned by callRemote
, then you
+ will never receive notification of the problem.
+
+ The class that is derived from pb.RemoteCopy
will be created using a
+ constructor __init__
method that takes no arguments. All
+ setup must be performed in the setCopyableState
method. As
+ the docstring on RemoteCopy
says, don't implement a
+ constructor that requires arguments in a subclass of
+ RemoteCopy
.
+
+
+
+
+
+
+
+
More Information
+
+
+
+ pb.Copyable
is mostly implemented
+ in twisted.spread.flavors
, and the docstrings there are
+ the best source of additional information.
+
+ Copyable
is also used in twisted.web.distrib
to deliver HTTP requests to other
+ programs for rendering, allowing subtrees of URL space to be delegated to
+ multiple programs (on multiple machines).
+
+ twisted.manhole.explorer
also uses
+ Copyable
to distribute debugging information from the program
+ under test to the debugging tool.
+
+
+
+
+
pb.Cacheable
+
+
Sometimes the object you want to send to the remote process is big and
+slow. big means it takes a lot of data (storage, network bandwidth,
+processing) to represent its state. slow means that state doesn't
+change very frequently. It may be more efficient to send the full state only
+once, the first time it is needed, then afterwards only send the differences
+or changes in state whenever it is modified. The pb.Cacheable
class provides a framework to
+implement this.
+
+
pb.Cacheable
is derived
+from pb.Copyable
, so it is
+based upon the idea of an object's state being captured on the sending side,
+and then turned into a new object on the receiving side. This is extended to
+have an object publishing on the sending side (derived from pb.Cacheable
), matched with one
+observing on the receiving side (derived from pb.RemoteCache
).
+
+
To effectively use pb.Cacheable
, you need to isolate changes
+to your object into accessor functions (specifically setter
+functions). Your object needs to get control every single time some
+attribute is changed3 .
+
+
You derive your sender-side class from pb.Cacheable
, and you
+add two methods: getStateToCacheAndObserveFor
+and stoppedObserving
. The first
+is called when a remote caching reference is first created, and retrieves
+the data with which the cache is first filled. It also provides an
+object called the observer 4 that points at that receiver-side cache. Every time the state of the object
+is changed, you give a message to the observer, informing them of the
+change. The other method, stoppedObserving
, is called when the
+remote cache goes away, so that you can stop sending updates.
+
+
On the receiver end, you make your cache class inherit from pb.RemoteCache
, and implement the
+ setCopyableState
as you would for a pb.RemoteCopy
+object. In addition, you must implement methods to receive the updates sent
+to the observer by the pb.Cacheable
: these methods should have
+names that start with observe_
, and match the
+ callRemote
invocations from the sender side just as the usual
+ remote_*
and perspective_*
methods match normal
+ callRemote
calls.
+
+
The first time a reference to the pb.Cacheable
object is
+sent to any particular recipient, a sender-side Observer will be created for
+it, and the getStateToCacheAndObserveFor
method will be called
+to get the current state and register the Observer. The state which that
+returns is sent to the remote end and turned into a local representation
+using setCopyableState
just like pb.RemoteCopy
,
+described above (in fact it inherits from that class).
+
+
After that, your setter functions on the sender side should call
+ callRemote
on the Observer, which causes observe_*
+methods to run on the receiver, which are then supposed to update the
+receiver-local (cached) state.
+
+
When the receiver stops following the cached object and the last
+reference goes away, the pb.RemoteCache
object can be freed.
+Just before it dies, it tells the sender side it no longer cares about the
+original object. When that reference count goes to zero, the
+Observer goes away and the pb.Cacheable
object can stop
+announcing every change that takes place. The stoppedObserving
method is
+used to tell the pb.Cacheable
that the Observer has gone
+away.
+
+
With the pb.Cacheable
and pb.RemoteCache
+classes in place, bound together by a call to
+ pb.setUnjellyableForClass
, all that remains is to pass a
+reference to your pb.Cacheable
over the wire to the remote end.
+The corresponding pb.RemoteCache
object will automatically be
+created, and the matching methods will be used to keep the receiver-side
+slave object in sync with the sender-side master object.
+
+
Example
+
+
Here is a complete example, in which the MasterDuckPond
is
+controlled by the sending side, and the SlaveDuckPond
is a
+cache that tracks changes to the master:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
+
+
+
+
+from twisted .spread import pb
+
+class MasterDuckPond (pb .Cacheable ):
+ def __init__ (self , ducks ):
+ self .observers = []
+ self .ducks = ducks
+ def count (self ):
+ print "I have [%d] ducks" % len (self .ducks )
+ def addDuck (self , duck ):
+ self .ducks .append (duck )
+ for o in self .observers : o .callRemote ('addDuck' , duck )
+ def removeDuck (self , duck ):
+ self .ducks .remove (duck )
+ for o in self .observers : o .callRemote ('removeDuck' , duck )
+ def getStateToCacheAndObserveFor (self , perspective , observer ):
+ self .observers .append (observer )
+
+ return self .ducks
+ def stoppedObserving (self , perspective , observer ):
+ self .observers .remove (observer )
+
+class SlaveDuckPond (pb .RemoteCache ):
+
+ def count (self ):
+ return len (self .cacheducks )
+ def getDucks (self ):
+ return self .cacheducks
+ def setCopyableState (self , state ):
+ print " cache - sitting, er, setting ducks"
+ self .cacheducks = state
+ def observe_addDuck (self , newDuck ):
+ print " cache - addDuck"
+ self .cacheducks .append (newDuck )
+ def observe_removeDuck (self , deadDuck ):
+ print " cache - removeDuck"
+ self .cacheducks .remove (deadDuck )
+
+pb .setUnjellyableForClass (MasterDuckPond , SlaveDuckPond )
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+
+
+
+
+
+from twisted .spread import pb , jelly
+from twisted .python import log
+from twisted .internet import reactor
+from cache_classes import MasterDuckPond
+
+class Sender :
+ def __init__ (self , pond ):
+ self .pond = pond
+
+ def phase1 (self , remote ):
+ self .remote = remote
+ d = remote .callRemote ("takePond" , self .pond )
+ d .addCallback (self .phase2 ).addErrback (log .err )
+ def phase2 (self , response ):
+ self .pond .addDuck ("ugly duckling" )
+ self .pond .count ()
+ reactor .callLater (1 , self .phase3 )
+ def phase3 (self ):
+ d = self .remote .callRemote ("checkDucks" )
+ d .addCallback (self .phase4 ).addErrback (log .err )
+ def phase4 (self , dummy ):
+ self .pond .removeDuck ("one duck" )
+ self .pond .count ()
+ self .remote .callRemote ("checkDucks" )
+ d = self .remote .callRemote ("ignorePond" )
+ d .addCallback (self .phase5 )
+ def phase5 (self , dummy ):
+ d = self .remote .callRemote ("shutdown" )
+ d .addCallback (self .phase6 )
+ def phase6 (self , dummy ):
+ reactor .stop ()
+
+def main ():
+ master = MasterDuckPond (["one duck" , "two duck" ])
+ master .count ()
+
+ sender = Sender (master )
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ deferred = factory .getRootObject ()
+ deferred .addCallback (sender .phase1 )
+ reactor .run ()
+
+if __name__ == '__main__' :
+ main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
+
+
+
+
+from twisted .application import service , internet
+from twisted .internet import reactor
+from twisted .spread import pb
+import cache_classes
+
+class Receiver (pb .Root ):
+ def remote_takePond (self , pond ):
+ self .pond = pond
+ print "got pond:" , pond
+ self .remote_checkDucks ()
+ def remote_checkDucks (self ):
+ print "[%d] ducks: " % self .pond .count (), self .pond .getDucks ()
+ def remote_ignorePond (self ):
+
+ print "dropping pond"
+
+ self .pond = None
+ def remote_shutdown (self ):
+ reactor .stop ()
+
+application = service .Application ("copy_receiver" )
+internet .TCPServer (8800 , pb .PBServerFactory (Receiver ())).setServiceParent (
+ service .IServiceCollection (application ))
+
+
When run, this example emits the following:
+
+
+$ twistd -n -y cache_receiver.py
+[-] twisted.spread.pb.PBServerFactory starting on 8800
+[-] Starting factory <twisted.spread.pb.PBServerFactory instance at
+0x40615acc>
+[Broker,0,127.0.0.1] cache - sitting, er, setting ducks
+[Broker,0,127.0.0.1] got pond: <cache_classes.SlaveDuckPond instance at
+0x406eb5ec>
+[Broker,0,127.0.0.1] [2] ducks: ['one duck', 'two duck']
+[Broker,0,127.0.0.1] cache - addDuck
+[Broker,0,127.0.0.1] [3] ducks: ['one duck', 'two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] cache - removeDuck
+[Broker,0,127.0.0.1] [2] ducks: ['two duck', 'ugly duckling']
+[Broker,0,127.0.0.1] dropping pond
+
+
+
+$ ./cache_sender.py
+I have [2] ducks
+I have [3] ducks
+I have [2] ducks
+Main loop terminated.
+
+
+
+
Points to notice:
+
+
+
+
+
+
+
More Information
+
+
+ The best source for information comes from the docstrings
+ in twisted.spread.flavors
,
+ where pb.Cacheable
is implemented.
+
+ twisted.manhole.explorer
uses
+ Cacheable
, and does some fairly interesting things with it.
+
+ The spread.publish
module also
+ uses Cacheable
, and might be a source of further
+ information.
+
+
+
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-cred.html b/doc/core/howto/pb-cred.html
new file mode 100644
index 0000000..1599806
--- /dev/null
+++ b/doc/core/howto/pb-cred.html
@@ -0,0 +1,1724 @@
+
+
+Twisted Documentation: Authentication with Perspective Broker
+
+
+
+
+ Authentication with Perspective Broker
+
+
+
+
+
Overview
+
+
The examples shown in Using Perspective
+Broker demonstrate how to do basic remote method calls, but provided no
+facilities for authentication. In this context, authentication is about who
+gets which remote references, and how to restrict access to the right
+set of people or programs.
+
+
As soon as you have a program which offers services to multiple users,
+where those users should not be allowed to interfere with each other, you
+need to think about authentication. Many services use the idea of an
+account , and rely upon fact that each user has access to only one
+account. Twisted uses a system called cred to
+handle authentication issues, and Perspective Broker has code to make it
+easy to implement the most common use cases.
+
+
Compartmentalizing Services
+
+
Imagine how you would write a chat server using PB. The first step might
+be a ChatServer
object which had a bunch of
+ pb.RemoteReference
s that point at user clients. Pretend that
+those clients offered a remote_print
method which lets the
+server print a message on the user's console. In that case, the server might
+look something like this:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+
class ChatServer (pb .Referenceable ):
+
+ def __init__ (self ):
+ self .groups = {}
+ self .users = {}
+ def remote_joinGroup (self , username , groupname ):
+ if not self .groups .has_key (groupname ):
+ self .groups [groupname ] = []
+ self .groups [groupname ].append (self .users [username ])
+ def remote_sendMessage (self , from_username , groupname , message ):
+ group = self .groups [groupname ]
+ if group :
+
+ for user in group :
+ user .callRemote ("print" ,
+ "<%s> says: %s" % (from_username ,
+ message ))
+
+
+
For now, assume that all clients have somehow acquired a
+ pb.RemoteReference
to this ChatServer
object,
+perhaps using pb.Root
and getRootObject
as
+described in the previous chapter . In this
+scheme, when a user sends a message to the group, their client runs
+something like the following:
+
+
1
+
remotegroup .callRemote ("sendMessage" , "alice" , "Hi, my name is alice." )
+
+
+
+
Incorrect Arguments
+
+
You've probably seen the first problem: users can trivially spoof each
+other. We depend upon the user to pass a correct value in their
+ username argument, and have no way to tell if they're lying or not.
+There is nothing to prevent Alice from modifying her client to do:
+
+
1
+
remotegroup .callRemote ("sendMessage" , "bob" , "i like pork" )
+
+
+
much to the horror of Bob's vegetarian friends.1
+
+
(In general, learn to get suspicious if you see any argument of a
+remotely-invokable method described as must be X )
+
+
The best way to fix this is to keep track of the user's name locally,
+rather than asking them to send it to the server with each message. The best
+place to keep state is in an object, so this suggests we need a per-user
+object. Rather than choosing an obvious name2 , let's call this the
+ User
class.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
class User (pb .Referenceable ):
+ def __init__ (self , username , server , clientref ):
+ self .name = username
+ self .server = server
+ self .remote = clientref
+ def remote_joinGroup (self , groupname ):
+ self .server .joinGroup (groupname , self )
+ def remote_sendMessage (self , groupname , message ):
+ self .server .sendMessage (self .name , groupname , message )
+ def send (self , message ):
+ self .remote .callRemote ("print" , message )
+
+class ChatServer :
+ def __init__ (self ):
+ self .groups = {}
+ def joinGroup (self , groupname , user ):
+ if not self .groups .has_key (groupname ):
+ self .groups [groupname ] = []
+ self .groups [groupname ].append (user )
+ def sendMessage (self , from_username , groupname , message ):
+ group = self .groups [groupname ]
+ if group :
+
+ for user in group :
+ user .send ("<%s> says: %s" % (from_username , message ))
+
+
+
Again, assume that each remote client gets access to a single
+ User
object, which is created with the proper username.
+
+
Note how the ChatServer
object has no remote access: it
+isn't even pb.Referenceable
anymore. This means that all access
+to it must be mediated through other objects, with code that is under your
+control.
+
+
As long as Alice only has access to her own User
object, she
+can no longer spoof Bob. The only way for her to invoke
+ ChatServer.sendMessage
is to call her User
+object's remote_sendMessage
method, and that method uses its
+own state to provide the from_username
argument. It doesn't
+give her any way to change that state.
+
+
This restriction is important. The User
object is able to
+maintain its own integrity because there is a wall between the object and
+the client: the client cannot inspect or modify internal state, like the
+ .name
attribute. The only way through this wall is via remote
+method invocations, and the only control Alice has over those invocations is
+when they get invoked and what arguments they are given.
+
+
Note:
+
No object can maintain its integrity against local threats: by design,
+Python offers no mechanism for class instances to hide their attributes, and
+once an intruder has a copy of self.__dict__
, they can do
+everything the original object was able to do.
+
+
+
+
Unforgeable References
+
+
Now suppose you wanted to implement group parameters, for example a mode
+in which nobody was allowed to talk about mattresses because some users were
+sensitive and calming them down after someone said mattress is a
+hassle that's best avoided altogether. Again, per-group state implies a
+per-group object. We'll go out on a limb and call this the
+ Group
object:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
class User (pb .Referenceable ):
+ def __init__ (self , username , server , clientref ):
+ self .name = username
+ self .server = server
+ self .remote = clientref
+ def remote_joinGroup (self , groupname , allowMattress =True ):
+ return self .server .joinGroup (groupname , self , allowMattress )
+ def send (self , message ):
+ self .remote .callRemote ("print" , message )
+
+class Group (pb .Referenceable ):
+ def __init__ (self , groupname , allowMattress ):
+ self .name = groupname
+ self .allowMattress = allowMattress
+ self .users = []
+ def remote_send (self , from_user , message ):
+ if not self .allowMattress and "mattress" in message :
+ raise ValueError , "Don't say that word"
+ for user in self .users :
+ user .send ("<%s> says: %s" % (from_user .name , message ))
+ def addUser (self , user ):
+ self .users .append (user )
+
+class ChatServer :
+ def __init__ (self ):
+ self .groups = {}
+ def joinGroup (self , groupname , user , allowMattress ):
+ if groupname not in self .groups :
+ self .groups [groupname ] = Group (groupname , allowMattress )
+ self .groups [groupname ].addUser (user )
+ return self .groups [groupname ]
+
+
+
+
This example takes advantage of the fact that
+ pb.Referenceable
objects sent over a wire can be returned to
+you, and they will be turned into references to the same object that you
+originally sent. The client cannot modify the object in any way: all they
+can do is point at it and invoke its remote_*
methods. Thus,
+you can be sure that the .name
attribute remains the same as
+you left it. In this case, the client code would look something like
+this:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
class ClientThing (pb .Referenceable ):
+ def remote_print (self , message ):
+ print message
+ def join (self ):
+ d = self .remoteUser .callRemote ("joinGroup" , "#twisted" ,
+ allowMattress =False )
+ d .addCallback (self .gotGroup )
+ def gotGroup (self , group ):
+ group .callRemote ("send" , self .remoteUser , "hi everybody" )
+
+
+
The User
object is sent from the server side, and is turned
+into a pb.RemoteReference
when it arrives at the client. The
+client sends it back to Group.remote_send
, and PB turns it back
+into a reference to the original User
when it gets there.
+ Group.remote_send
can then use its .name
attribute
+as the sender of the message.
+
+
Note:
+
+
Third party references (there aren't any)
+
+
This technique also relies upon the fact that the
+ pb.Referenceable
reference can only come from someone
+who holds a corresponding pb.RemoteReference
. The design of the
+serialization mechanism (implemented in twisted.spread.jelly
: pb, jelly, spread.. get it? Look for
+banana , too. What other networking framework
+can claim API names based on sandwich ingredients?) makes it impossible for
+a client to obtain a reference that they weren't explicitly given.
+References passed over the wire are given id numbers and recorded in a
+per-connection dictionary. If you didn't give them the reference, the id
+number won't be in the dict, and no amount of guessing by a malicious client
+will give them anything else. The dict goes away when the connection is
+dropped, further limiting the scope of those references.
+
+
Futhermore, it is not possible for Bob to send his
+ User
reference to Alice (perhaps over some other PB channel
+just between the two of them). Outside the context of Bob's connection to
+the server, that reference is just a meaningless number. To prevent
+confusion, PB will tell you if you try to give it away: when you try to hand
+a pb.RemoteReference
to a third party, you'll get an exception
+(implemented with an assert in pb.py:364 RemoteReference.jellyFor).
+
+
This helps the security model somewhat: only the client you gave the
+reference to can cause any damage with it. Of course, the client might be a
+brainless zombie, simply doing anything some third party wants. When it's
+not proxying callRemote
invocations, it's probably terrorizing
+the living and searching out human brains for sustenance. In short, if you
+don't trust them, don't give them that reference.
+
+
And remember that everything you've ever given them over that connection
+can come back to you. If expect the client to invoke your method with some
+object A that you sent to them earlier, and instead they send you object B
+(that you also sent to them earlier), and you don't check it somehow, then
+you've just opened up a security hole (we'll see an example of this
+shortly). It may be better to keep such objects in a dictionary on the
+server side, and have the client send you an index string instead. Doing it
+that way makes it obvious that they can send you anything they want, and
+improves the chances that you'll remember to implement the right checks.
+(This is exactly what PB is doing underneath, with a per-connection
+dictionary of Referenceable
objects, indexed by a number).
+
+
And, of course, you have to make sure you don't accidentally hand out a
+reference to the wrong object.
+
+
+
+
+
But again, note the vulnerability. If Alice holds a
+ RemoteReference
to any object on the server side that
+has a .name
attribute, she can use that name as a spoofed
+from parameter. As a simple example, what if her client code looked
+like:
+
+
1
+2
+3
+4
+5
+6
+
class ClientThing (pb .Referenceable ):
+ def join (self ):
+ d = self .remoteUser .callRemote ("joinGroup" , "#twisted" )
+ d .addCallback (self .gotGroup )
+ def gotGroup (self , group ):
+ group .callRemote ("send" , from_user =group , "hi everybody" )
+
+
+
This would let her send a message that appeared to come from
+#twisted rather than Alice . If she joined a group that
+happened to be named bob (perhaps it is the How To Be Bob
+channel, populated by Alice and countless others, a place where they can
+share stories about their best impersonating-Bob moments), then she would be
+able to emit a message that looked like <bob> says: hi there ,
+and she has accomplished her lifelong goal.
+
+
+
Argument Typechecking
+
+
There are two techniques to close this hole. The first is to have your
+remotely-invokable methods do type-checking on their arguments: if
+ Group.remote_send
asserted isinstance(from_user,
+User)
then Alice couldn't use non-User objects to do her spoofing,
+and hopefully the rest of the system is designed well enough to prevent her
+from obtaining access to somebody else's User object.
+
+
+
Objects as Capabilities
+
+
The second technique is to avoid having the client send you the objects
+altogether. If they don't send you anything, there is nothing to verify. In
+this case, you would have to have a per-user-per-group object, in which the
+ remote_send
method would only take a single
+ message
argument. The UserGroup
object is created
+with references to the only User
and Group
objects
+that it will ever use, so no lookups are needed:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
class UserGroup (pb .Referenceable ):
+ def __init__ (self , user , group ):
+ self .user = user
+ self .group = group
+ def remote_send (self , message ):
+ self .group .send (self .user .name , message )
+
+class Group :
+ def __init__ (self , groupname , allowMattress ):
+ self .name = groupname
+ self .allowMattress = allowMattress
+ self .users = []
+ def send (self , from_user , message ):
+ if not self .allowMattress and "mattress" in message :
+ raise ValueError , "Don't say that word"
+ for user in self .users :
+ user .send ("<%s> says: %s" % (from_user .name , message ))
+ def addUser (self , user ):
+ self .users .append (user )
+
+
+
The only message-sending method Alice has left is
+ UserGroup.remote_send
, and it only accepts a message: there are
+no remaining ways to influence the from name.
+
+
In this model, each remotely-accessible object represents a very small
+set of capabilities. Security is achieved by only granting a minimal set of
+abilities to each remote user.
+
+
PB provides a shortcut which makes this technique easier to use. The
+ Viewable
class will be discussed below .
+
+
Avatars and Perspectives
+
+
In Twisted's cred system, an Avatar is
+an object that lives on the server side (defined here as the side
+farthest from the human who is trying to get something done) which lets the
+remote user get something done. The avatar isn't really a particular class,
+it's more like a description of a role that some object plays, as in the
+Foo object here is acting as the user's avatar for this particular
+service . Generally, the remote user has some way of getting their avatar
+to run some code. The avatar object may enforce some security checks, and
+provide additional data, then call other methods which get things done.
+
+
The two pieces in the cred puzzle (for any protocol, not just PB) are:
+what serves as the Avatar? , and how does the user get access to
+it? .
+
+
For PB, the first question is easy. The Avatar is a remotely-accessible
+object which can run code: this is a perfect description of
+ pb.Referenceable
and its subclasses. We shall defer the second
+question until the next section.
+
+
In the example above, you can think of the ChatServer
and
+ Group
objects as a service. The User
object is the
+user's server-side representative: everything the user is capable of doing
+is done by running one of its methods. Anything that the server wants to do
+to the user (change their group membership, change their name, delete their
+pet cat, whatever) is done by manipulating the User
object.
+
+
There are multiple User objects living in peace and harmony around the
+ChatServer. Each has a different point of view on the services provided by
+the ChatServer and the Groups: each may belong to different groups, some
+might have more permissions than others (like the ability to create groups).
+These different points of view are called Perspectives . This is the
+origin of the term Perspective in Perspective Broker : PB
+provides and controls (i.e. brokers ) access to Perspectives.
+
+
Once upon a time, these local-representative objects were actually called
+ pb.Perspective
. But this has changed with the advent of the
+rewritten cred system, and now the more generic term for a local
+representative object is an Avatar. But you will still see reference to
+ Perspective in the code, the docs, and the module names3 . Just remember
+that perspectives and avatars are basically the same thing.
+
+
Despite all we've been telling you about how
+Avatars are more of a concept than an actual class, the base class from
+which you can create your server-side avatar-ish objects is, in fact, named
+ pb.Avatar
4 . These objects behave very much like
+ pb.Referenceable
. The only difference is that instead of
+offering remote_FOO methods, they offer perspective_FOO
+methods.
+
+
The other way in which pb.Avatar
differs from
+ pb.Referenceable
is that the avatar objects are designed to be
+the first thing retrieved by a cred-using remote client. Just as
+ PBClientFactory.getRootObject
gives the client access to a
+ pb.Root
object (which can then provide access to all kinds of
+other objects), PBClientFactory.login
gives client access to a
+ pb.Avatar
object (which can return other references).
+
+
So, the first half of using cred in your PB application is to create an
+Avatar object which implements perspective_
methods and is
+careful to do useful things for the remote user while remaining vigilant
+against being tricked with unexpected argument values. It must also be
+careful to never give access to objects that the user should not have access
+to, whether by returning them directly, returning objects which contain
+them, or returning objects which can be asked (remotely) to provide
+them.
+
+
The second half is how the user gets a pb.RemoteReference
to
+your Avatar. As explained elsewhere , Avatars are
+obtained from a Realm. The Realm doesn't deal with authentication at all
+(usernames, passwords, public keys, challenge-response systems, retinal
+scanners, real-time DNA sequencers, etc). It simply takes an avatarID
+(which is effectively a username) and returns an Avatar object. The Portal
+and its Checkers deal with authenticating the user: by the time they are
+done, the remote user has proved their right to access the avatarID that is
+given to the Realm, so the Realm can return a remotely-controllable object
+that has whatever powers you wish to grant to this particular user.
+
+
For PB, the realm is expected to return a pb.Avatar
(or
+anything which implements pb.IPerspective
, really, but there's
+no reason to not return a pb.Avatar
subclass). This object will
+be given to the client just like a pb.Root
would be without
+cred, and the user can get access to other objects through it (if you let
+them).
+
+
The basic idea is that there is a separate IPerspective-implementing
+object (i.e. the Avatar subclass) (i.e. the perspective ) for each
+user, and only the authorized user gets a remote reference to that
+object. You can store whatever permissions or capabilities the user
+possesses in that object, and then use them when the user invokes a remote
+method. You give the user access to the perspective object instead of the
+objects that do the real work.
+
+
+
Perspective Examples
+
+
Here is a brief example of using a pb.Avatar. Most of the support code
+is magic for now: we'll explain it later.
+
+
One Client
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
+
+
+
+
+from zope .interface import implements
+
+from twisted .spread import pb
+from twisted .cred import checkers , portal
+from twisted .internet import reactor
+
+class MyPerspective (pb .Avatar ):
+ def __init__ (self , name ):
+ self .name = name
+ def perspective_foo (self , arg ):
+ print "I am" , self .name , "perspective_foo(" ,arg ,") called on" , self
+
+class MyRealm :
+ implements (portal .IRealm )
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces :
+ raise NotImplementedError
+ return pb .IPerspective , MyPerspective (avatarId ), lambda :None
+
+p = portal .Portal (MyRealm ())
+p .registerChecker (
+ checkers .InMemoryUsernamePasswordDatabaseDontUse (user1 ="pass1" ))
+reactor .listenTCP (8800 , pb .PBServerFactory (p ))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .cred import credentials
+
+def main ():
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .login (credentials .UsernamePassword ("user1" , "pass1" ))
+ def1 .addCallback (connected )
+ reactor .run ()
+
+def connected (perspective ):
+ print "got perspective ref:" , perspective
+ print "asking it to foo(12)"
+ perspective .callRemote ("foo" , 12 )
+
+main ()
+
+
+
Ok, so that wasn't really very exciting. It doesn't accomplish much more
+than the first PB example, and used a lot more code to do it. Let's try it
+again with two users this time.
+
+
Note:
+
+
When the client runs login
to request the Perspective,
+they can provide it with an optional client
argument (which
+must be a pb.Referenceable
object). If they do, then a
+reference to that object will be handed to the realm's
+ requestAvatar
in the mind
argument.
+
+
The server-side Perspective can use it to invoke remote methods on
+something in the client, so that the client doesn't always have to drive the
+interaction. In a chat server, the client object would be the one to which
+display text messages were sent. In a board game server, this would
+provide a way to tell the clients that someone has made a move, so they can
+update their game boards.
+
+
+
+
Two Clients
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
+
+
+
+from zope .interface import implements
+
+from twisted .spread import pb
+from twisted .cred import checkers , portal
+from twisted .internet import reactor
+
+class MyPerspective (pb .Avatar ):
+ def __init__ (self , name ):
+ self .name = name
+ def perspective_foo (self , arg ):
+ print "I am" , self .name , "perspective_foo(" ,arg ,") called on" , self
+
+class MyRealm :
+ implements (portal .IRealm )
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if pb .IPerspective not in interfaces :
+ raise NotImplementedError
+ return pb .IPerspective , MyPerspective (avatarId ), lambda :None
+
+p = portal .Portal (MyRealm ())
+c = checkers .InMemoryUsernamePasswordDatabaseDontUse (user1 ="pass1" ,
+ user2 ="pass2" )
+p .registerChecker (c )
+reactor .listenTCP (8800 , pb .PBServerFactory (p ))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .cred import credentials
+
+def main ():
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .login (credentials .UsernamePassword ("user1" , "pass1" ))
+ def1 .addCallback (connected )
+ reactor .run ()
+
+def connected (perspective ):
+ print "got perspective1 ref:" , perspective
+ print "asking it to foo(13)"
+ perspective .callRemote ("foo" , 13 )
+
+main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .cred import credentials
+
+def main ():
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .login (credentials .UsernamePassword ("user2" , "pass2" ))
+ def1 .addCallback (connected )
+ reactor .run ()
+
+def connected (perspective ):
+ print "got perspective2 ref:" , perspective
+ print "asking it to foo(14)"
+ perspective .callRemote ("foo" , 14 )
+
+main ()
+
+
+
While pb6server.py is running, try starting pb6client1, then pb6client2.
+Compare the argument passed by the .callRemote()
in each
+client. You can see how each client gets connected to a different
+Perspective.
+
+
+
How that example worked
+
+
Let's walk through the previous example and see what was going on.
+
+
First, we created a subclass called MyPerspective
which is
+our server-side Avatar. It implements a perspective_foo
method
+that is exposed to the remote client.
+
+
Second, we created a realm (an object which implements
+ IRealm
, and therefore implements requestAvatar
).
+This realm manufactures MyPerspective
objects. It makes as many
+as we want, and names each one with the avatarID (a username) that comes out
+of the checkers. This MyRealm object returns two other objects as well,
+which we will describe later.
+
+
Third, we created a portal to hold this realm. The portal's job is to
+dispatch incoming clients to the credential checkers, and then to request
+Avatars for any which survive the authentication process.
+
+
Fourth, we made a simple checker (an object which implements
+ IChecker
) to hold valid user/password pairs. The checker
+gets registered with the portal, so it knows who to ask when new
+clients connect. We use a checker named
+ InMemoryUsernamePasswordDatabaseDontUse
, which suggests
+that 1: all the username/password pairs are kept in memory instead of
+being saved to a database or something, and 2: you shouldn't use
+it. The admonition against using it is because there are better
+schemes: keeping everything in memory will not work when you have
+thousands or millions of users to keep track of, the passwords will be
+stored in the .tap file when the application shuts down (possibly a
+security risk), and finally it is a nuisance to add or remove users
+after the checker is constructed.
+
+
Fifth, we create a pb.PBServerFactory
to listen on a TCP
+port. This factory knows how to connect the remote client to the Portal, so
+incoming connections will be handed to the authentication process. Other
+protocols (non-PB) would do something similar: the factory that creates
+Protocol objects will give those objects access to the Portal so
+authentication can take place.
+
+
On the client side, a pb.PBClientFactory
is created (as before ) and attached to a TCP connection. When the
+connection completes, the factory will be asked to produce a Protocol, and
+it will create a PB object. Unlike the previous chapter, where we used
+ .getRootObject
, here we use factory.login
to
+ initiate the cred authentication process. We provide a
+ credentials
object, which is the client-side agent for doing
+our half of the authentication process. This process may involve several
+messages: challenges, responses, encrypted passwords, secure hashes, etc. We
+give our credentials object everything it will need to respond correctly (in
+this case, a username and password, but you could write a credential that
+used public-key encryption or even fancier techniques).
+
+
login
returns a Deferred which, when it fires, will return a
+ pb.RemoteReference
to the remote avatar. We can then do
+ callRemote
to invoke a perspective_foo
method on
+that Avatar.
+
+
+
Anonymous Clients
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+
+
+
+
+
+"""
+Implement the realm for and run on port 8800 a PB service which allows both
+anonymous and username/password based access.
+
+Successful username/password-based login requests given an instance of
+MyPerspective with a name which matches the username with which they
+authenticated. Success anonymous login requests are given an instance of
+MyPerspective with the name "Anonymous".
+"""
+
+from sys import stdout
+
+from zope .interface import implements
+
+from twisted .python .log import startLogging
+from twisted .cred .checkers import ANONYMOUS , AllowAnonymousAccess
+from twisted .cred .checkers import InMemoryUsernamePasswordDatabaseDontUse
+from twisted .cred .portal import IRealm , Portal
+from twisted .internet import reactor
+from twisted .spread .pb import Avatar , IPerspective , PBServerFactory
+
+
+class MyPerspective (Avatar ):
+ """
+ Trivial avatar exposing a single remote method for demonstrative
+ purposes. All successful login attempts in this example will result in
+ an avatar which is an instance of this class.
+
+ @type name: C{str}
+ @ivar name: The username which was used during login or C{"Anonymous"}
+ if the login was anonymous (a real service might want to avoid the
+ collision this introduces between anonoymous users and authenticated
+ users named "Anonymous").
+ """
+ def __init__ (self , name ):
+ self .name = name
+
+
+ def perspective_foo (self , arg ):
+ """
+ Print a simple message which gives the argument this method was
+ called with and this avatar's name.
+ """
+ print "I am %s. perspective_foo(%s) called on %s." % (
+ self .name , arg , self )
+
+
+
+class MyRealm (object ):
+ """
+ Trivial realm which supports anonymous and named users by creating
+ avatars which are instances of MyPerspective for either.
+ """
+ implements (IRealm )
+
+ def requestAvatar (self , avatarId , mind , *interfaces ):
+ if IPerspective not in interfaces :
+ raise NotImplementedError ("MyRealm only handles IPerspective" )
+ if avatarId is ANONYMOUS :
+ avatarId = "Anonymous"
+ return IPerspective , MyPerspective (avatarId ), lambda : None
+
+
+
+def main ():
+ """
+ Create a PB server using MyRealm and run it on port 8800.
+ """
+ startLogging (stdout )
+
+ p = Portal (MyRealm ())
+
+
+ c1 = InMemoryUsernamePasswordDatabaseDontUse (user1 ="pass1" , user2 ="pass2" )
+ p .registerChecker (c1 )
+
+
+ c2 = AllowAnonymousAccess ()
+ p .registerChecker (c2 )
+
+ reactor .listenTCP (8800 , PBServerFactory (p ))
+ reactor .run ()
+
+
+if __name__ == '__main__' :
+ main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+
+
+
+
+
+"""
+Client which will talk to the server run by pbAnonServer.py, logging in
+either anonymously or with username/password credentials.
+"""
+
+from sys import stdout
+
+from twisted .python .log import err , startLogging
+from twisted .cred .credentials import Anonymous , UsernamePassword
+from twisted .internet import reactor
+from twisted .internet .defer import gatherResults
+from twisted .spread .pb import PBClientFactory
+
+
+def error (why , msg ):
+ """
+ Catch-all errback which simply logs the failure. This isn't expected to
+ be invoked in the normal case for this example.
+ """
+ err (why , msg )
+
+
+def connected (perspective ):
+ """
+ Login callback which invokes the remote "foo" method on the perspective
+ which the server returned.
+ """
+ print "got perspective1 ref:" , perspective
+ print "asking it to foo(13)"
+ return perspective .callRemote ("foo" , 13 )
+
+
+def finished (ignored ):
+ """
+ Callback invoked when both logins and method calls have finished to shut
+ down the reactor so the example exits.
+ """
+ reactor .stop ()
+
+
+def main ():
+ """
+ Connect to a PB server running on port 8800 on localhost and log in to
+ it, both anonymously and using a username/password it will recognize.
+ """
+ startLogging (stdout )
+ factory = PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+
+ anonymousLogin = factory .login (Anonymous ())
+ anonymousLogin .addCallback (connected )
+ anonymousLogin .addErrback (error , "Anonymous login failed" )
+
+ usernameLogin = factory .login (UsernamePassword ("user1" , "pass1" ))
+ usernameLogin .addCallback (connected )
+ usernameLogin .addErrback (error , "Username/password login failed" )
+
+ bothDeferreds = gatherResults ([anonymousLogin , usernameLogin ])
+ bothDeferreds .addCallback (finished )
+
+ reactor .run ()
+
+
+if __name__ == '__main__' :
+ main ()
+
+
+
pbAnonServer.py implements a server based on pb6server.py, extending it to
+permit anonymous logins in addition to authenticated logins. An
+ AllowAnonymousAccess
+checker and an InMemoryUsernamePasswordDatabaseDontUse
+checker are registered and the
+client's choice of credentials object determines which is used to authenticate
+the login. In either case, the realm will be called on to create an avatar for
+the login. AllowAnonymousAccess
always produces an avatarId
+
of twisted.cred.checkers.ANONYMOUS
.
+
+
On the client side, the only change is the use of an instance of
+ Anonymous
when calling
+ PBClientFactory.login
.
+
+
+
Using Avatars
+
+
+
Avatar Interfaces
+
+
The first element of the 3-tuple returned by requestAvatar
+indicates which Interface this Avatar implements. For PB avatars, it will
+always be pb.IPerspective
, because that's the only interface
+these avatars implement.
+
+
This element is present because requestAvatar
is actually
+presented with a list of possible Interfaces. The question being posed to
+the Realm is: do you have an avatar for (avatarID) that can implement one
+of the following set of Interfaces? . Some portals and checkers might
+give a list of Interfaces and the Realm could pick; the PB code only knows
+how to do one, so we cannot take advantage of this feature.
+
+
Logging Out
+
+
The third element of the 3-tuple is a zero-argument callable, which will
+be invoked by the protocol when the connection has been lost. We can use
+this to notify the Avatar when the client has lost its connection. This will
+be described in more detail below.
+
+
Making Avatars
+
+
In the example above, we create Avatars upon request, during
+ requestAvatar
. Depending upon the service, these Avatars might
+already exist before the connection is received, and might outlive the
+connection. The Avatars might also accept multiple connections.
+
+
Another possibility is that the Avatars might exist ahead of time, but in
+a different form (frozen in a pickle and/or saved in a database). In this
+case, requestAvatar
may need to perform a database lookup and
+then do something with the result before it can provide an avatar. In this
+case, it would probably return a Deferred so it could provide the real
+Avatar later, once the lookup had completed.
+
+
Here are some possible implementations of
+ MyRealm.requestAvatar
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ avatar = self .avatars [avatarID ]
+ return pb .IPerspective , avatar , lambda :None
+
+
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ d = self .database .fetchAvatar (avatarID )
+ d .addCallback (self .doUnpickle )
+ return pb .IPerspective , d , lambda :None
+ def doUnpickle (self , pickled ):
+ avatar = pickle .loads (pickled )
+ return avatar
+
+
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ return pb .IPerspective , self .theOneAvatar , lambda :None
+
+
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ if avatarID == checkers .ANONYMOUS :
+ return pb .IPerspective , self .anonAvatar , lambda :None
+ else :
+ return pb .IPerspective , self .avatars [avatarID ], lambda :None
+
+
+
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ if avatarID == checkers .ANONYMOUS :
+ return pb .IPerspective , MyAvatar (), lambda :None
+ else :
+ return pb .IPerspective , self .avatars [avatarID ], lambda :None
+
+
+
The last example, note that the new MyAvatar
instance is not
+saved anywhere: it will vanish when the connection is dropped. By contrast,
+the avatars that live in the self.avatars
dictionary will
+probably get persisted into the .tap file along with the Realm, the Portal,
+and anything else that is referenced by the top-level Application object.
+This is an easy way to manage saved user profiles.
+
+
+
Connecting and Disconnecting
+
+
It may be useful for your Avatars to be told when remote clients gain
+(and lose) access to them. For example, and Avatar might be updated by
+something in the server, and if there are clients attached, it should update
+them (through the mind argument which lets the Avatar do callRemote
+on the client).
+
+
One common idiom which accomplishes this is to have the Realm tell the
+avatar that a remote client has just attached. The Realm can also ask the
+protocol to let it know when the connection goes away, so it can then inform
+the Avatar that the client has detached. The third member of the
+ requestAvatar
return tuple is a callable which will be invoked
+when the connection is lost.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
class MyPerspective (pb .Avatar ):
+ def __init__ (self ):
+ self .clients = []
+ def attached (self , mind ):
+ self .clients .append (mind )
+ print "attached to" , mind
+ def detached (self , mind ):
+ self .clients .remove (mind )
+ print "detached from" , mind
+ def update (self , message ):
+ for c in self .clients :
+ c .callRemote ("update" , message )
+
+class MyRealm :
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ avatar = self .avatars [avatarID ]
+ avatar .attached (mind )
+ return pb .IPerspective , avatar , lambda a =avatar :a .detached (mind )
+
+
+
+
Viewable
+
+
Once you have IPerspective
objects (i.e. the Avatar) to
+represent users, the Viewable
class can come into play. This
+class behaves a lot like Referenceable
: it turns into a
+ RemoteReference
when sent over the wire, and certain methods
+can be invoked by the holder of that reference. However, the methods that
+can be called have names that start with view_
instead of remote_
, and those methods are always called with an extra
+perspective
argument that points to the Avatar through which
+the reference was sent:
+
+
1
+2
+3
+
class Foo (pb .Viewable ):
+ def view_doFoo (self , perspective , arg1 , arg2 ):
+ pass
+
+
+
This is useful if you want to let multiple clients share a reference to
+the same object. The view_
methods can use the
+ perspective argument to figure out which client is calling them. This
+gives them a way to do additional permission checks, do per-user accounting,
+etc.
+
+
This is the shortcut which makes per-user-per-group capability objects
+much easier to use. Instead of creating such per-(user,group) objects, you
+just have per-group objects which inherit from pb.Viewable
, and
+give the user references to them. The local pb.Avatar
object
+will automatically show up as the perspective argument in the
+ view_*
method calls, give you a chance to involve the Avatar in
+the process.
+
+
+
Chat Server with Avatars
+
+
Combining all the above techniques, here is an example chat server which
+uses a fixed set of identities (say, for the three members of your bridge
+club, who hang out in #NeedAFourth hoping that someone will discover
+your server, guess somebody's password, break in, join the group, and also
+be available for a game next saturday afternoon).
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+
+
+
+
+
+from zope .interface import implements
+
+from twisted .cred import portal , checkers
+from twisted .spread import pb
+from twisted .internet import reactor
+
+class ChatServer :
+ def __init__ (self ):
+ self .groups = {}
+
+ def joinGroup (self , groupname , user , allowMattress ):
+ if not self .groups .has_key (groupname ):
+ self .groups [groupname ] = Group (groupname , allowMattress )
+ self .groups [groupname ].addUser (user )
+ return self .groups [groupname ]
+
+class ChatRealm :
+ implements (portal .IRealm )
+ def requestAvatar (self , avatarID , mind , *interfaces ):
+ assert pb .IPerspective in interfaces
+ avatar = User (avatarID )
+ avatar .server = self .server
+ avatar .attached (mind )
+ return pb .IPerspective , avatar , lambda a =avatar :a .detached (mind )
+
+class User (pb .Avatar ):
+ def __init__ (self , name ):
+ self .name = name
+ def attached (self , mind ):
+ self .remote = mind
+ def detached (self , mind ):
+ self .remote = None
+ def perspective_joinGroup (self , groupname , allowMattress =True ):
+ return self .server .joinGroup (groupname , self , allowMattress )
+ def send (self , message ):
+ self .remote .callRemote ("print" , message )
+
+class Group (pb .Viewable ):
+ def __init__ (self , groupname , allowMattress ):
+ self .name = groupname
+ self .allowMattress = allowMattress
+ self .users = []
+ def addUser (self , user ):
+ self .users .append (user )
+ def view_send (self , from_user , message ):
+ if not self .allowMattress and "mattress" in message :
+ raise ValueError , "Don't say that word"
+ for user in self .users :
+ user .send ("<%s> says: %s" % (from_user .name , message ))
+
+realm = ChatRealm ()
+realm .server = ChatServer ()
+checker = checkers .InMemoryUsernamePasswordDatabaseDontUse ()
+checker .addUser ("alice" , "1234" )
+checker .addUser ("bob" , "secret" )
+checker .addUser ("carol" , "fido" )
+p = portal .Portal (realm , [checker ])
+
+reactor .listenTCP (8800 , pb .PBServerFactory (p ))
+reactor .run ()
+
+
+
Notice that the client uses perspective_joinGroup
to both
+join a group and retrieve a RemoteReference
to the
+ Group
object. However, the reference they get is actually to a
+special intermediate object called a pb.ViewPoint
. When they do
+ group.callRemote("send", "message")
, their avatar is inserted
+into the argument list that Group.view_send
actually sees. This
+lets the group get their username out of the Avatar without giving the
+client an opportunity to spoof someone else.
+
+
The client side code that joins a group and sends a message would look
+like this:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .cred import credentials
+
+class Client (pb .Referenceable ):
+
+ def remote_print (self , message ):
+ print message
+
+ def connect (self ):
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .login (credentials .UsernamePassword ("alice" , "1234" ),
+ client =self )
+ def1 .addCallback (self .connected )
+ reactor .run ()
+
+ def connected (self , perspective ):
+ print "connected, joining group #NeedAFourth"
+
+
+
+ self .perspective = perspective
+ d = perspective .callRemote ("joinGroup" , "#NeedAFourth" )
+ d .addCallback (self .gotGroup )
+
+ def gotGroup (self , group ):
+ print "joined group, now sending a message to all members"
+
+ d = group .callRemote ("send" , "You can call me Al." )
+ d .addCallback (self .shutdown )
+
+ def shutdown (self , result ):
+ reactor .stop ()
+
+
+Client ().connect ()
+
+
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-intro.html b/doc/core/howto/pb-intro.html
new file mode 100644
index 0000000..dead735
--- /dev/null
+++ b/doc/core/howto/pb-intro.html
@@ -0,0 +1,320 @@
+
+
+Twisted Documentation: Introduction to Perspective Broker
+
+
+
+
+ Introduction to Perspective Broker
+
+
+
+
+
Introduction
+
+
Suppose you find yourself in control of both ends of the wire: you
+have two programs that need to talk to each other, and you get to use any
+protocol you want. If you can think of your problem in terms of objects that
+need to make method calls on each other, then chances are good that you can
+use Twisted's Perspective Broker protocol rather than trying to shoehorn
+your needs into something like HTTP, or implementing yet another RPC
+mechanism1 .
+
+
The Perspective Broker system (abbreviated PB , spawning numerous
+sandwich-related puns) is based upon a few central concepts:
+
+
+
+ serialization : taking fairly arbitrary objects and types,
+ turning them into a chunk of bytes, sending them over a wire, then
+ reconstituting them on the other end. By keeping careful track of object
+ ids, the serialized objects can contain references to other objects and
+ the remote copy will still be useful.
+
+ remote method calls : doing something to a local object and
+ causing a method to get run on a distant one. The local object is called a
+ RemoteReference
, and you
+ do something by running its .callRemote
method.
+
+
+
+
+
This document will contain several examples that will (hopefully) appear
+redundant and verbose once you've figured out what's going on. To begin
+with, much of the code will just be labelled magic : don't worry about how
+these parts work yet. It will be explained more fully later.
+
+
Object Roadmap
+
+
To start with, here are the major classes, interfaces, and
+functions involved in PB, with links to the file where they are
+defined (all of which are under twisted/, of course). Don't worry
+about understanding what they all do yet: it's easier to figure them
+out through their interaction than explaining them one at a time.
+
+
+
+
Other classes that are involved at some point:
+
+
+
+ RemoteReference
+ : spread/pb.py
+
+ pb.Root
+ : spread/pb.py
, actually defined as
+ twisted.spread.flavors.Root
+ in spread/flavors.py
+
+ pb.Referenceable
+ : spread/pb.py
, actually defined as
+ twisted.spread.flavors.Referenceable
+ in spread/flavors.py
+
+
+
+
Classes and interfaces that get involved when you start to care
+about authorization and security:
+
+
+ Portal
+ : cred/portal.py
+
+ IRealm
+ : cred/portal.py
+
+ IPerspective
+ : spread/pb.py
, which you will usually be interacting
+ with via pb.Avatar
(a basic implementor of the interface).
+
+
+
Subclassing and Implementing
+
+
Technically you can subclass anything you want, but technically you
+could also write a whole new framework, which would just waste a lot
+of time. Knowing which classes are useful to subclass or which
+interfaces to implement is one of the bits of knowledge that's crucial
+to using PB (and all of Twisted) successfully. Here are some hints to
+get started:
+
+
+
+ pb.Root
, pb.Referenceable
: you'll
+ subclass these to make remotely-referenceable objects (i.e., objects
+ which you can call methods on remotely) using PB. You don't need to
+ change any of the existing behavior, just inherit all of it and add
+ the remotely-accessible methods that you want to export.
+
+ pb.Avatar
: You'll
+ be subclassing this when you get into PB programming with
+ authorization. This is an implementor of IPerspective.
+
+ ICredentialsChecker
: Implement this if
+ you want to authenticate your users against some sort of data store:
+ i.e., an LDAP database, an RDBMS, etc. There are already a few
+ implementations of this for various back-ends in
+ twisted.cred.checkers.
+
+
+
+
+
+
Things you can Call Remotely
+
+
At this writing, there are three flavors of objects that can
+be accessed remotely through RemoteReference
objects. Each of these
+flavors has a rule for how the callRemote
+message is transformed into a local method call on the server. In
+order to use one of these flavors , subclass them and name your
+published methods with the appropriate prefix.
+
+
+ twisted.spread.pb.IPerspective
implementors
+
+ This is the first interface we deal with. It is a perspective
+ onto your PB application. Perspectives are slightly special because
+ they are usually the first object that a given user can access in
+ your application (after they log on). A user should only receive a
+ reference to their own perspective. PB works hard to
+ verify, as best it can, that any method that can be called on a
+ perspective directly is being called on behalf of the user who is
+ represented by that perspective. (Services with unusual
+ requirements for on behalf of , such as simulations with the
+ ability to posess another player's avatar, are accomplished by
+ providing indirected access to another user's perspective.)
+
+
+
+ Perspectives are not usually serialized as remote references, so
+ do not return an IPerspective-implementor directly.
+
+ The way most people will want to implement IPerspective is by
+ subclassing pb.Avatar. Remotely accessible methods on pb.Avatar
+ instances are named with the perspective_
prefix.
+
+
+
+ twisted.spread.pb.Referenceable
+
+ Referenceable objects are the simplest kind of PB object. You can call
+ methods on them and return them from methods to provide access to other
+ objects' methods.
+
+ However, when a method is called on a Referenceable, it's not possible to
+ tell who called it.
+
+ Remotely accessible methods on Referenceables are named with the
+ remote_
prefix.
+
+
+
+ twisted.spread.pb.Viewable
+
+ Viewable objects are remotely referenceable objects which have the
+ additional requirement that it must be possible to tell who is calling them.
+ The argument list to a Viewable's remote methods is modified in order to
+ include the Perspective representing the calling user.
+
+ Remotely accessible methods on Viewables are named with the
+ view_
prefix.
+
+
+
+
+
+
+
+
Things you can Copy Remotely
+
+
In addition to returning objects that you can call remote methods on, you
+can return structured copies of local objects.
+
+
There are 2 basic flavors that allow for copying objects remotely. Again,
+you can use these by subclassing them. In order to specify what state you want
+to have copied when these are serialized, you can either use the Python default
+ __getstate__
or specialized method calls for that
+flavor.
+
+
+
+ twisted.spread.pb.Copyable
+
+ This is the simpler kind of object that can be copied. Every time this
+ object is returned from a method or passed as an argument, it is serialized
+ and unserialized.
+
+ Copyable
+ provides a method you can override, getStateToCopyFor(perspective)
, which
+ allows you to decide what an object will look like for the
+ perspective who is requesting it. The perspective
argument will be the perspective
+ which is either passing an argument or returning a result an
+ instance of your Copyable class.
+
+ For security reasons, in order to allow a particular Copyable class to
+ actually be copied, you must declare a RemoteCopy
+ handler for
+ that Copyable subclass. The easiest way to do this is to declare both in the
+ same module, like so:
+
+
1
+2
+3
+4
+5
+6
+
from twisted .spread import flavors
+class Foo (flavors .Copyable ):
+ pass
+class RemoteFoo (flavors .RemoteCopy ):
+ pass
+flavors .setUnjellyableForClass (Foo , RemoteFoo )
+
+
+ In this case, each time a Foo is copied between peers, a RemoteFoo will be
+ instantiated and populated with the Foo's state. If you do not do this, PB
+ will complain that there have been security violations, and it may close the
+ connection.
+
+
+
+
+ twisted.spread.pb.Cacheable
+
+ Let me preface this with a warning: Cacheable may be hard to understand.
+ The motivation for it may be unclear if you don't have some experience with
+ real-world applications that use remote method calling of some kind. Once
+ you understand why you need it, what it does will likely seem simple and
+ obvious, but if you get confused by this, forget about it and come back
+ later. It's possible to use PB without understanding Cacheable at all.
+
+
+ Cacheable is a flavor which is designed to be copied only when necessary,
+ and updated on the fly as changes are made to it. When passed as an argument
+ or a return value, if a Cacheable exists on the side of the connection it is
+ being copied to, it will be referred to by ID and not copied.
+
+ Cacheable is designed to minimize errors involved in replicating an object
+ between multiple servers, especially those related to having stale
+ information. In order to do this, Cacheable automatically registers
+ observers and queries state atomically, together. You can override the
+ method getStateToCacheAndObserveFor(self,
+ perspective, observer)
in order to specify how your observers will be
+ stored and updated.
+
+
+ Similar to
+ getStateToCopyFor
,
+ getStateToCacheAndObserveFor
gets passed a
+ perspective. It also gets passed an
+ observer
, which is a remote reference to a
+ secret fourth referenceable flavor:
+ RemoteCache
.
+
+ A RemoteCache
is simply
+ the object that represents your
+ Cacheable
on the other side
+ of the connection. It is registered using the same method as
+ RemoteCopy
, above.
+ RemoteCache is different, however, in that it will be referenced by its peer.
+ It acts as a Referenceable, where all methods prefixed with
+ observe_
will be callable remotely. It is
+ recommended that your object maintain a list (note: library support for this
+ is forthcoming!) of observers, and update them using
+ callRemote
when the Cacheable changes in a way
+ that should be noticeable to its clients.
+
+ Finally, when all references to a
+ Cacheable
from a given
+ perspective are lost,
+ stoppedObserving(perspective, observer)
+ will be called on the
+ Cacheable
, with the same
+ perspective/observer pair that getStateToCacheAndObserveFor
was
+ originally called with. Any cleanup remote calls can be made there, as well
+ as removing the observer object from any lists which it was previously in.
+ Any further calls to this observer object will be invalid.
+
+
+
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-limits.html b/doc/core/howto/pb-limits.html
new file mode 100644
index 0000000..b92a38d
--- /dev/null
+++ b/doc/core/howto/pb-limits.html
@@ -0,0 +1,51 @@
+
+
+Twisted Documentation: PB Limits
+
+
+
+
+ PB Limits
+
+
+
+
+
There are a number of limits you might encounter when using Perspective
+Broker. This document is an attempt to prepare you for as many of them as
+possible so you can avoid them or at least recognize them when you do run
+into them.
+
+
Banana Limits
+
+
Perspective Broker is implemented in terms of a simpler, less
+functional protocol called Banana. Twisted's implementation of Banana
+imposes a limit on the length of any sequence-like data type. This applies
+directly to lists and strings and indirectly to dictionaries, instances and
+other types. The purpose of this limit is to put an upper bound on the
+amount of memory which will be allocated to handle a message received over
+the network. Without, a malicious peer could easily perform a denial of
+service attack resulting in exhaustion of the receiver's memory. The basic
+limit is 640 * 1024 bytes, defined by twisted.spread.banana.SIZE_LIMIT
.
+It's possible to raise this limit by changing this value (but take care to
+change it on both sides of the connection).
+
+
Another limit imposed by Twisted's Banana implementation is a limit on
+the size of long integers. The purpose of this limit is the same as the
+ SIZE_LIMIT
. By default, only integers between -2 ** 448 and 2
+** 448 (exclusive) can be transferred. This limit can be changed using
+ twisted.spread.banana.setPrefixLimit
.
+
+
Perspective Broker Limits
+
+
Perspective Broker imposes an additional limit on top of these lower
+level limits. The number of local objects for which remote references may
+exist at a single time over a single connection, by default, is limited to
+1024, defined by twisted.spread.pb.MAX_BROKER_REFS
. This limit
+also exists to prevent memory exhaustion attacks.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb-usage.html b/doc/core/howto/pb-usage.html
new file mode 100644
index 0000000..fb8cb70
--- /dev/null
+++ b/doc/core/howto/pb-usage.html
@@ -0,0 +1,1156 @@
+
+
+Twisted Documentation: Using Perspective Broker
+
+
+
+
+ Using Perspective Broker
+
+
+
+
+
Basic Example
+
+
The first example to look at is a complete (although somewhat trivial)
+application. It uses PBServerFactory()
on the server side, and
+ PBClientFactory()
on the client side.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .spread import pb
+from twisted .internet import reactor
+
+class Echoer (pb .Root ):
+ def remote_echo (self , st ):
+ print 'echoing:' , st
+ return st
+
+if __name__ == '__main__' :
+ reactor .listenTCP (8789 , pb .PBServerFactory (Echoer ()))
+ reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
from twisted .spread import pb
+from twisted .internet import reactor
+from twisted .python import util
+
+factory = pb .PBClientFactory ()
+reactor .connectTCP ("localhost" , 8789 , factory )
+d = factory .getRootObject ()
+d .addCallback (lambda object : object .callRemote ("echo" , "hello network" ))
+d .addCallback (lambda echo : 'server echoed: ' +echo )
+d .addErrback (lambda reason : 'error: ' +str (reason .value ))
+d .addCallback (util .println )
+d .addCallback (lambda _ : reactor .stop ())
+reactor .run ()
+
+
+
First we look at the server. This defines an Echoer class (derived from
+ pb.Root
), with a method called
+ remote_echo()
.
+ pb.Root
objects (because of
+their inheritance of
+ pb.Referenceable
, described
+later) can define methods with names of the form remote_*
; a
+client which obtains a remote reference to that
+ pb.Root
object will be able to
+invoke those methods.
+
+
The pb.Root
-ish object is
+given to a pb.PBServerFactory
()
. This is a
+ Factory
object like
+any other: the Protocol
objects it creates for new
+connections know how to speak the PB protocol. The object you give to
+ pb.PBServerFactory()
becomes the root object , which
+simply makes it available for the client to retrieve. The client may only
+request references to the objects you want to provide it: this helps you
+implement your security model. Because it is so common to export just a
+single object (and because a remote_*
method on that one can
+return a reference to any other object you might want to give out), the
+simplest example is one where the PBServerFactory
is given the root object, and
+the client retrieves it.
+
+
The client side uses
+ pb.PBClientFactory
to make a
+connection to a given port. This is a two-step process involving opening
+a TCP connection to a given host and port and requesting the root object
+using .getRootObject()
.
+
+
Because .getRootObject()
has to wait until a network
+connection has been made and exchange some data, it may take a while,
+so it returns a Deferred, to which the gotObject() callback is
+attached. (See the documentation on Deferring
+Execution for a complete explanation of Deferred
s). If and when the
+connection succeeds and a reference to the remote root object is
+obtained, this callback is run. The first argument passed to the
+callback is a remote reference to the distant root object. (you can
+give other arguments to the callback too, see the other parameters for
+ .addCallback()
and .addCallbacks()
).
+
+
The callback does:
+
+
1
+
object .callRemote ("echo" , "hello network" )
+
+
+
which causes the server's .remote_echo()
method to be invoked.
+(running .callRemote("boom")
would cause
+ .remote_boom()
to be run, etc). Again because of the delay
+involved, callRemote()
returns a
+ Deferred
. Assuming the
+remote method was run without causing an exception (including an attempt to
+invoke an unknown method), the callback attached to that
+ Deferred
will be
+invoked with any objects that were returned by the remote method call.
+
+
In this example, the server's Echoer
object has a method
+invoked, exactly as if some code on the server side had done:
+
+
1
+
echoer_object .remote_echo ("hello network" )
+
+
+
and from the definition of remote_echo()
we see that this just
+returns the same string it was given: hello network .
+
+
From the client's point of view, the remote call gets another Deferred
object instead of
+that string. callRemote()
always returns a Deferred
. This is why PB is
+described as a system for translucent remote method calls instead of
+transparent ones: you cannot pretend that the remote object is really
+local. Trying to do so (as some other RPC mechanisms do, coughCORBAcough)
+breaks down when faced with the asynchronous nature of the network. Using
+Deferreds turns out to be a very clean way to deal with the whole thing.
+
+
The remote reference object (the one given to
+ getRootObject()
's success callback) is an instance the RemoteReference
class. This means
+you can use it to invoke methods on the remote object that it refers to. Only
+instances of RemoteReference
are eligible for
+ .callRemote()
. The RemoteReference
object is the one that lives
+on the remote side (the client, in this case), not the local side (where the
+actual object is defined).
+
+
In our example, the local object is that Echoer()
instance,
+which inherits from pb.Root
,
+which inherits from
+ pb.Referenceable
. It is that
+ Referenceable
class that makes the object eligible to be available
+for remote method calls1 . If you have
+an object that is Referenceable, then any client that manages to get a
+reference to it can invoke any remote_*
methods they please.
+
+
Note:
+
The only thing they can do is invoke those
+methods. In particular, they cannot access attributes. From a security point
+of view, you control what they can do by limiting what the
+remote_*
methods can do.
+
+
Also note: the other classes like
+ Referenceable
allow access to
+other methods, in particular perspective_*
and view_*
+may be accessed. Don't write local-only methods with these names, because then
+remote callers will be able to do more than you intended.
+
+
Also also note: the other classes like
+ pb.Copyable
do allow
+access to attributes, but you control which ones they can see.
+
+
+
You don't have to be a
+ pb.Root
to be remotely callable,
+but you do have to be
+ pb.Referenceable
. (Objects that
+inherit from pb.Referenceable
+but not from pb.Root
can be
+remotely called, but only
+ pb.Root
-ish objects can be given
+to the PBServerFactory
.)
+
+
Complete Example
+
+
Here is an example client and server which uses pb.Referenceable
as a root object and as the
+result of a remotely exposed method. In each context, methods can be invoked
+on the exposed Referenceable
+instance. In this example, the initial root object has a method that returns a
+reference to the second object.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+
+
+
+
+from twisted .spread import pb
+
+class Two (pb .Referenceable ):
+ def remote_three (self , arg ):
+ print "Two.three was given" , arg
+
+class One (pb .Root ):
+ def remote_getTwo (self ):
+ two = Two ()
+ print "returning a Two called" , two
+ return two
+
+from twisted .internet import reactor
+reactor .listenTCP (8800 , pb .PBServerFactory (One ()))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+def main ():
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .getRootObject ()
+ def1 .addCallbacks (got_obj1 , err_obj1 )
+ reactor .run ()
+
+def err_obj1 (reason ):
+ print "error getting first object" , reason
+ reactor .stop ()
+
+def got_obj1 (obj1 ):
+ print "got first object:" , obj1
+ print "asking it to getTwo"
+ def2 = obj1 .callRemote ("getTwo" )
+ def2 .addCallbacks (got_obj2 )
+
+def got_obj2 (obj2 ):
+ print "got second object:" , obj2
+ print "telling it to do three(12)"
+ obj2 .callRemote ("three" , 12 )
+
+main ()
+
+
+
pb.PBClientFactory.getRootObject
will
+handle all the details of waiting for the creation of a connection.
+It returns a Deferred
, which will have its
+callback called when the reactor connects to the remote server and
+ pb.PBClientFactory
gets the
+root, and have its errback
called when the
+object-connection fails for any reason, whether it was host lookup
+failure, connection refusal, or some server-side error.
+
+
+
The root object has a method called remote_getTwo
, which
+returns the Two()
instance. On the client end, the callback gets
+a RemoteReference
to that
+instance. The client can then invoke two's .remote_three()
+method.
+
+
RemoteReference
+objects have one method which is their purpose for being: callRemote
. This method allows you to call a
+remote method on the object being referred to by the Reference. RemoteReference.callRemote
, like pb.PBClientFactory.getRootObject
, returns
+a Deferred
.
+When a response to the method-call being sent arrives, the Deferred
's callback
or errback
+will be made, depending on whether an error occurred in processing the
+method call.
+
+
You can use this technique to provide access to arbitrary sets of objects.
+Just remember that any object that might get passed over the wire must
+inherit from Referenceable
+(or one of the other flavors). If you try to pass a non-Referenceable object
+(say, by returning one from a remote_*
method), you'll get an
+ InsecureJelly
+exception2 .
+
+
+
References can come back to you
+
+
If your server gives a reference to a client, and then that client gives
+the reference back to the server, the server will wind up with the same
+object it gave out originally. The serialization layer watches for returning
+reference identifiers and turns them into actual objects. You need to stay
+aware of where the object lives: if it is on your side, you do actual method
+calls. If it is on the other side, you do
+ .callRemote()
3 .
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+class Two (pb .Referenceable ):
+ def remote_print (self , arg ):
+ print "two.print was given" , arg
+
+class One (pb .Root ):
+ def __init__ (self , two ):
+
+ self .two = two
+ def remote_getTwo (self ):
+ print "One.getTwo(), returning my two called" , self .two
+ return self .two
+ def remote_checkTwo (self , newtwo ):
+ print "One.checkTwo(): comparing my two" , self .two
+ print "One.checkTwo(): against your two" , newtwo
+ if self .two == newtwo :
+ print "One.checkTwo(): our twos are the same"
+
+
+two = Two ()
+root_obj = One (two )
+reactor .listenTCP (8800 , pb .PBServerFactory (root_obj ))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+def main ():
+ foo = Foo ()
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ factory .getRootObject ().addCallback (foo .step1 )
+ reactor .run ()
+
+
+
+
+
+class Foo :
+ def __init__ (self ):
+ self .oneRef = None
+
+ def step1 (self , obj ):
+ print "got one object:" , obj
+ self .oneRef = obj
+ print "asking it to getTwo"
+ self .oneRef .callRemote ("getTwo" ).addCallback (self .step2 )
+
+ def step2 (self , two ):
+ print "got two object:" , two
+ print "giving it back to one"
+ print "one is" , self .oneRef
+ self .oneRef .callRemote ("checkTwo" , two )
+
+main ()
+
+
+
The server gives a Two()
instance to the client, who then
+returns the reference back to the server. The server compares the two
+given with the two received and shows that they are the same, and that
+both are real objects instead of remote references.
+
+
A few other techniques are demonstrated in pb2client.py
. One
+is that the callbacks are are added with .addCallback
instead
+of .addCallbacks
. As you can tell from the Deferred documentation, .addCallback
is a
+simplified form which only adds a success callback. The other is that to
+keep track of state from one callback to the next (the remote reference to
+the main One() object), we create a simple class, store the reference in an
+instance thereof, and point the callbacks at a sequence of bound methods.
+This is a convenient way to encapsulate a state machine. Each response kicks
+off the next method, and any data that needs to be carried from one state to
+the next can simply be saved as an attribute of the object.
+
+
Remember that the client can give you back any remote reference you've
+given them. Don't base your zillion-dollar stock-trading clearinghouse
+server on the idea that you trust the client to give you back the right
+reference. The security model inherent in PB means that they can only
+give you back a reference that you've given them for the current connection
+(not one you've given to someone else instead, nor one you gave them last
+time before the TCP session went down, nor one you haven't yet given to the
+client), but just like with URLs and HTTP cookies, the particular reference
+they give you is entirely under their control.
+
+
+
References to client-side objects
+
+
Anything that's Referenceable can get passed across the wire, in
+either direction . The client can give a reference to the
+ server , and then the server can use .callRemote() to invoke methods on
+the client end. This fuzzes the distinction between client and
+ server : the only real difference is who initiates the original TCP
+connection; after that it's all symmetric.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+class One (pb .Root ):
+ def remote_takeTwo (self , two ):
+ print "received a Two called" , two
+ print "telling it to print(12)"
+ two .callRemote ("print" , 12 )
+
+reactor .listenTCP (8800 , pb .PBServerFactory (One ()))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+class Two (pb .Referenceable ):
+ def remote_print (self , arg ):
+ print "Two.print() called with" , arg
+
+def main ():
+ two = Two ()
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ def1 = factory .getRootObject ()
+ def1 .addCallback (got_obj , two )
+ reactor .run ()
+
+def got_obj (obj , two ):
+ print "got One:" , obj
+ print "giving it our two"
+ obj .callRemote ("takeTwo" , two )
+
+main ()
+
+
+
In this example, the client gives a reference to its own object to the
+server. The server then invokes a remote method on the client-side
+object.
+
+
+
Raising Remote Exceptions
+
+
Everything so far has covered what happens when things go right. What
+about when they go wrong? The Python Way is to raise an exception of some
+sort. The Twisted Way is the same.
+
+
The only special thing you do is to define your Exception
+subclass by deriving it from pb.Error
. When any remotely-invokable method
+(like remote_*
or perspective_*
) raises a
+ pb.Error
-derived exception, a serialized form of that Exception
+object will be sent back over the wire4 . The other side (which
+did callRemote
) will have the errback
+callback run with a Failure
object that contains a copy of
+the exception object. This Failure
object can be queried to
+retrieve the error message and a stack traceback.
+
+
Failure
is a
+special class, defined in twisted/python/failure.py
, created to
+make it easier to handle asynchronous exceptions. Just as exception handlers
+can be nested, errback
functions can be chained. If one errback
+can't handle the particular type of failure, it can be passed along to a
+errback handler further down the chain.
+
+
For simple purposes, think of the Failure
as just a container
+for remotely-thrown Exception
objects. To extract the string that
+was put into the exception, use its .getErrorMessage()
method.
+To get the type of the exception (as a string), look at its
+ .type
attribute. The stack traceback is available too. The
+intent is to let the errback function get just as much information about the
+exception as Python's normal try:
clauses do, even though the
+exception occurred in somebody else's memory space at some unknown time in
+the past.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+class MyError (pb .Error ):
+ """This is an Expected Exception. Something bad happened."""
+ pass
+
+class MyError2 (Exception ):
+ """This is an Unexpected Exception. Something really bad happened."""
+ pass
+
+class One (pb .Root ):
+ def remote_broken (self ):
+ msg = "fall down go boom"
+ print "raising a MyError exception with data '%s'" % msg
+ raise MyError (msg )
+ def remote_broken2 (self ):
+ msg = "hadda owie"
+ print "raising a MyError2 exception with data '%s'" % msg
+ raise MyError2 (msg )
+
+def main ():
+ reactor .listenTCP (8800 , pb .PBServerFactory (One ()))
+ reactor .run ()
+
+if __name__ == '__main__' :
+ main ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+def main ():
+ factory = pb .PBClientFactory ()
+ reactor .connectTCP ("localhost" , 8800 , factory )
+ d = factory .getRootObject ()
+ d .addCallbacks (got_obj )
+ reactor .run ()
+
+def got_obj (obj ):
+
+ d2 = obj .callRemote ("broken" )
+ d2 .addCallback (working )
+ d2 .addErrback (broken )
+
+def working ():
+ print "erm, it wasn't *supposed* to work.."
+
+def broken (reason ):
+ print "got remote Exception"
+
+ print " .__class__ =" , reason .__class__
+ print " .getErrorMessage() =" , reason .getErrorMessage ()
+ print " .type =" , reason .type
+ reactor .stop ()
+
+main ()
+
+
+
+$ ./exc_client.py
+got remote Exception
+ .__class__ = twisted.spread.pb.CopiedFailure
+ .getErrorMessage() = fall down go boom
+ .type = __main__.MyError
+Main loop terminated.
+
+
+
Oh, and what happens if you raise some other kind of exception? Something
+that isn't subclassed from pb.Error
? Well, those are
+called unexpected exceptions , which make Twisted think that something
+has really gone wrong. These will raise an exception on the
+ server side. This won't break the connection (the exception is
+trapped, just like most exceptions that occur in response to network
+traffic), but it will print out an unsightly stack trace on the server's
+stderr with a message that says Peer Will Receive PB Traceback , just
+as if the exception had happened outside a remotely-invokable method. (This
+message will go the current log target, if log.startLogging
was used to redirect it). The
+client will get the same Failure
object in either case, but
+subclassing your exception from pb.Error
is the way to tell
+Twisted that you expect this sort of exception, and that it is ok to just
+let the client handle it instead of also asking the server to complain. Look
+at exc_client.py
and change it to invoke broken2()
+instead of broken()
to see the change in the server's
+behavior.
+
+
If you don't add an errback
function to the Deferred
, then a remote
+exception will still send a Failure
object back over, but it
+will get lodged in the Deferred
with nowhere to go. When that
+ Deferred
finally goes out of scope, the side that did
+ callRemote
will emit a message about an Unhandled error in
+Deferred , along with an ugly stack trace. It can't raise an exception at
+that point (after all, the callRemote
that triggered the
+problem is long gone), but it will emit a traceback. So be a good programmer
+and always add errback
handlers, even if they are just
+calls to log.err
.
+
+
+
+
To implement the equivalent of the Python try/except blocks (which can
+trap particular kinds of exceptions and pass others up to
+higher-level try/except
blocks), you can use the
+ .trap()
method in conjunction with multiple
+ errback
handlers on the Deferred
. Re-raising an
+exception in an errback
handler serves to pass that new
+exception to the next handler in the chain. The trap
method is
+given a list of exceptions to look for, and will re-raise anything that
+isn't on the list. Instead of passing unhandled exceptions up to an
+enclosing try
block, this has the effect of passing the
+exception off to later errback
handlers on the same
+Deferred
. The trap
calls are used in chained
+errbacks to test for each kind of exception in sequence.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
+
+
+
+
+from twisted .internet import reactor
+from twisted .spread import pb
+
+class MyException (pb .Error ):
+ pass
+
+class One (pb .Root ):
+ def remote_fooMethod (self , arg ):
+ if arg == "panic!" :
+ raise MyException
+ return "response"
+ def remote_shutdown (self ):
+ reactor .stop ()
+
+reactor .listenTCP (8800 , pb .PBServerFactory (One ()))
+reactor .run ()
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+
+
+
+
+
+from twisted .spread import pb , jelly
+from twisted .python import log
+from twisted .internet import reactor
+
+class MyException (pb .Error ): pass
+class MyOtherException (pb .Error ): pass
+
+class ScaryObject :
+
+ pass
+
+def worksLike (obj ):
+
+
+ try :
+ response = obj .callMethod (name , arg )
+ except pb .DeadReferenceError :
+ print " stale reference: the client disconnected or crashed"
+ except jelly .InsecureJelly :
+ print " InsecureJelly: you tried to send something unsafe to them"
+ except (MyException , MyOtherException ):
+ print " remote raised a MyException"
+ except :
+ print " something else happened"
+ else :
+ print " method successful, response:" , response
+
+class One :
+ def worked (self , response ):
+ print " method successful, response:" , response
+ def check_InsecureJelly (self , failure ):
+ failure .trap (jelly .InsecureJelly )
+ print " InsecureJelly: you tried to send something unsafe to them"
+ return None
+ def check_MyException (self , failure ):
+ which = failure .trap (MyException , MyOtherException )
+ if which == MyException :
+ print " remote raised a MyException"
+ else :
+ print " remote raised a MyOtherException"
+ return None
+ def catch_everythingElse (self , failure ):
+ print " something else happened"
+ log .err (failure )
+ return None
+
+ def doCall (self , explanation , arg ):
+ print explanation
+ try :
+ deferred = self .remote .callRemote ("fooMethod" , arg )
+ deferred .addCallback (self .worked )
+ deferred .addErrback (self .check_InsecureJelly )
+ deferred .addErrback (self .check_MyException )
+ deferred .addErrback (self .catch_everythingElse )
+ except pb .DeadReferenceError :
+ print " stale reference: the client disconnected or crashed"
+
+ def callOne (self ):
+ self .doCall ("callOne: call with safe object" , "safe string" )
+ def callTwo (self ):
+ self .doCall ("callTwo: call with dangerous object" , ScaryObject ())
+ def callThree (self ):
+ self .doCall ("callThree: call that raises remote exception" , "panic!" )
+ def callShutdown (self ):
+ print "telling them to shut down"
+ self .remote .callRemote ("shutdown" )
+ def callFour (self ):
+ self .doCall ("callFour: call on stale reference" , "dummy" )
+
+ def got_obj (self , obj ):
+ self .remote = obj
+ reactor .callLater (1 , self .callOne )
+ reactor .callLater (2 , self .callTwo )
+ reactor .callLater (3 , self .callThree )
+ reactor .callLater (4 , self .callShutdown )
+ reactor .callLater (5 , self .callFour )
+ reactor .callLater (6 , reactor .stop )
+
+factory = pb .PBClientFactory ()
+reactor .connectTCP ("localhost" , 8800 , factory )
+deferred = factory .getRootObject ()
+deferred .addCallback (One ().got_obj )
+reactor .run ()
+
+
+
+$ ./trap_client.py
+callOne: call with safe object
+ method successful, response: response
+callTwo: call with dangerous object
+ InsecureJelly: you tried to send something unsafe to them
+callThree: call that raises remote exception
+ remote raised a MyException
+telling them to shut down
+callFour: call on stale reference
+ stale reference: the client disconnected or crashed
+
+
+
+
In this example, callTwo
tries to send an instance of a
+locally-defined class through callRemote
. The default security
+model implemented by jelly
+on the remote end will not allow unknown classes to be unserialized (i.e.
+taken off the wire as a stream of bytes and turned back into an object: a
+living, breathing instance of some class): one reason is that it does not
+know which local class ought to be used to create an instance that
+corresponds to the remote object5 .
+
+
The receiving end of the connection gets to decide what to accept and what
+to reject. It indicates its disapproval by raising a jelly.InsecureJelly
exception. Because it occurs
+at the remote end, the exception is returned to the caller asynchronously,
+so an errback
handler for the associated Deferred
+is run. That errback receives a Failure
which wraps the
+ InsecureJelly
.
+
+
+
Remember that trap
re-raises exceptions that it wasn't asked
+to look for. You can only check for one set of exceptions per errback
+handler: all others must be checked in a subsequent handler.
+ check_MyException
shows how multiple kinds of exceptions can be
+checked in a single errback: give a list of exception types to
+ trap
, and it will return the matching member. In this case, the
+kinds of exceptions we are checking for (MyException
and
+ MyOtherException
) may be raised by the remote end: they inherit
+from pb.Error
.
+
+
The handler can return None
to terminate processing of the
+errback chain (to be precise, it switches to the callback that follows the
+errback; if there is no callback then processing terminates). It is a good
+idea to put an errback that will catch everything (no trap
+tests, no possible chance of raising more exceptions, always returns
+ None
) at the end of the chain. Just as with regular try:
+except:
handlers, you need to think carefully about ways in which
+your errback handlers could themselves raise exceptions. The extra
+importance in an asynchronous environment is that an exception that falls
+off the end of the Deferred
will not be signalled until that
+ Deferred
goes out of scope, and at that point may only cause a
+log message (which could even be thrown away if log.startLogging
is not used to point it at
+stdout or a log file). In contrast, a synchronous exception that is not
+handled by any other except:
block will very visibly terminate
+the program immediately with a noisy stack trace.
+
+
callFour
shows another kind of exception that can occur
+while using callRemote
: pb.DeadReferenceError
. This one occurs when the
+remote end has disconnected or crashed, leaving the local side with a stale
+reference. This kind of exception happens to be reported right away (XXX: is
+this guaranteed? probably not), so must be caught in a traditional
+synchronous try: except pb.DeadReferenceError
block.
+
+
Yet another kind that can occur is a pb.PBConnectionLost
exception. This occurs
+(asynchronously) if the connection was lost while you were waiting for a
+callRemote
call to complete. When the line goes dead, all
+pending requests are terminated with this exception. Note that you have no
+way of knowing whether the request made it to the other end or not, nor how
+far along in processing it they had managed before the connection was
+lost. XXX: explain transaction semantics, find a decent reference.
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/pb.html b/doc/core/howto/pb.html
new file mode 100644
index 0000000..f610dee
--- /dev/null
+++ b/doc/core/howto/pb.html
@@ -0,0 +1,52 @@
+
+
+Twisted Documentation: Overview of Twisted Spread
+
+
+
+
+ Overview of Twisted Spread
+
+
+
+
+
Perspective Broker (affectionately known as PB ) is an
+asynchronous, symmetric1 network protocol for secure,
+remote method calls and transferring of objects. PB is translucent, not
+transparent , meaning that it is very visible and obvious to see the
+difference between local method calls and potentially remote method calls,
+but remote method calls are still extremely convenient to make, and it is
+easy to emulate them to have objects which work both locally and
+remotely.
+
+
PB supports user-defined serialized data in return values, which can be
+either copied each time the value is returned, or cached : only copied
+once and updated by notifications.
+
+
PB gets its name from the fact that access to objects is through a
+perspective . This means that when you are responding to a remote
+method call, you can establish who is making the call.
+
+
Rationale
+
+
No other currently existing protocols have all the properties of PB at the
+same time. The particularly interesting combination of attributes, though, is
+that PB is flexible and lightweight, allowing for rapid development, while
+still powerful enough to do two-way method calls and user-defined data
+types.
+
+
It is important to have these attributes in order to allow for a protocol
+which is extensible. One of the facets of this flexibility is that PB can
+integrate an arbitrary number of services could be aggregated over a single
+connection, as well as publish and call new methods on existing objects
+without restarting the server or client.
+
+
Footnotes
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/plugin.html b/doc/core/howto/plugin.html
new file mode 100644
index 0000000..9aacdaa
--- /dev/null
+++ b/doc/core/howto/plugin.html
@@ -0,0 +1,294 @@
+
+
+Twisted Documentation: The Twisted Plugin System
+
+
+
+
+ The Twisted Plugin System
+
+
+
+
+
The purpose of this guide is to describe the preferred way to
+ write extensible Twisted applications (and consequently, also to
+ describe how to extend applications written in such a way). This
+ extensibility is achieved through the definition of one or more
+ APIs and a mechanism for collecting code plugins which
+ implement this API to provide some additional functionality.
+ At the base of this system is the twisted.plugin
module.
+
+
Making an application extensible using the plugin system has
+ several strong advantages over other techniques:
+
+
+ It allows third-party developers to easily enhance your
+ software in a way that is loosely coupled: only the plugin API
+ is required to remain stable.
+
+ It allows new plugins to be discovered flexibly. For
+ example, plugins can be loaded and saved when a program is first
+ run, or re-discovered each time the program starts up, or they
+ can be polled for repeatedly at runtime (allowing the discovery
+ of new plugins installed after the program has started).
+
+
+
Writing Extensible Programs
+
+
Taking advantage of twisted.plugin
is
+ a two step process:
+
+
+
+
+ Define an interface which plugins will be required to implement.
+ This is done using the zope.interface package in the same way one
+ would define an interface for any other purpose.
+
+
+
+ A convention for defining interfaces is do so in a file named like
+ ProjectName/projectname/iprojectname.py . The rest of this
+ document will follow that convention: consider the following
+ interface definition be in Matsim/matsim/imatsim.py
, an
+ interface definition module for a hypothetical material simulation
+ package.
+
+
+
+
+ At one or more places in your program, invoke twisted.plugin.getPlugins
and iterate over its
+ result.
+
+
+
+
+ As an example of the first step, consider the following interface
+ definition for a physical modelling system.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
from zope .interface import Interface , Attribute
+
+class IMaterial (Interface ):
+ """
+ An object with specific physical properties
+ """
+ def yieldStress (temperature ):
+ """
+ Returns the pressure this material can support without
+ fracturing at the given temperature.
+
+ @type temperature: C{float}
+ @param temperature: Kelvins
+
+ @rtype: C{float}
+ @return: Pascals
+ """
+
+ dielectricConstant = Attribute ("""
+ @type dielectricConstant: C{complex}
+ @ivar dielectricConstant: The relative permittivity, with the
+ real part giving reflective surface properties and the
+ imaginary part giving the radio absorption coefficient.
+ """ )
+
+
+
In another module, we might have a function that operates on
+ objects providing the IMaterial
interface:
+
+
1
+2
+3
+
def displayMaterial (m ):
+ print 'A material with yield stress %s at 500 K' % (m .yieldStress (500 ),)
+ print 'Also a dielectric constant of %s.' % (m .dielectricConstant ,)
+
+
+
The last piece of required code is that which collects
+ IMaterial
providers and passes them to the
+ displayMaterial
function.
+
+
1
+2
+3
+4
+5
+6
+
from twisted .plugin import getPlugins
+from matsim import imatsim
+
+def displayAllKnownMaterials ():
+ for material in getPlugins (imatsim .IMaterial ):
+ displayMaterial (material )
+
+
+
Third party developers may now contribute different materials
+ to be used by this modelling system by implementing one or more
+ plugins for the IMaterial
interface.
+
+
Extending an Existing Program
+
+
The above code demonstrates how an extensible program might be
+ written using Twisted's plugin system. How do we write plugins
+ for it, though? Essentially, we create objects which provide the
+ required interface and then make them available at a particular
+ location. Consider the following example.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
from zope .interface import implements
+from twisted .plugin import IPlugin
+from matsim import imatsim
+
+class SimpleMaterial (object ):
+ implements (IPlugin , imatsim .IMaterial )
+
+ def __init__ (self , yieldStressFactor , dielectricConstant ):
+ self ._yieldStressFactor = yieldStressFactor
+ self .dielectricConstant = dielectricConstant
+
+ def yieldStress (self , temperature ):
+ return self ._yieldStressFactor * temperature
+
+steelPlate = SimpleMaterial (2.06842719e11 , 2.7 + 0.2j )
+brassPlate = SimpleMaterial (1.03421359e11 , 1.4 + 0.5j )
+
+
+
steelPlate
and brassPlate
now provide both
+ IPlugin
and IMaterial
.
+ All that remains is to make this module available at an appropriate
+ location. For this, there are two options. The first of these is
+ primarily useful during development: if a directory which
+ has been added to sys.path
(typically by adding it to the
+ PYTHONPATH
environment variable) contains a
+ directory named twisted/plugins/
,
+ each .py
file in that directory will be loaded
+ as a source of plugins. This directory must not be a Python
+ package: including __init__.py
will cause the
+ directory to be skipped and no plugins loaded from it. Second, each
+ module in the installed version of Twisted's
+ twisted.plugins
package will also be loaded as a source of
+ plugins.
+
+
Once this plugin is installed in one of these two ways,
+ displayAllKnownMaterials
can be run and we will see
+ two pairs of output: one for a steel plate and one for a brass
+ plate.
+
+
Alternate Plugin Packages
+
+
getPlugins
takes one
+ additional argument not mentioned above. If passed in, the 2nd argument
+ should be a module or package to be used instead of
+ twisted.plugins
as the plugin meta-package. If you
+ are writing a plugin for a Twisted interface, you should never
+ need to pass this argument. However, if you have developed an
+ interface of your own, you may want to mandate that plugins for it
+ are installed in your own plugins package, rather than in
+ Twisted's.
+
+
You may want to support yourproject/plugins/
+ directories for ease of development. To do so, you should make yourproject/plugins/__init__.py
contain at least
+ the following lines.
+
+
1
+2
+3
+
from twisted .plugin import pluginPackagePaths
+__path__ .extend (pluginPackagePaths (__name__ ))
+__all__ = []
+
+
+
The key behavior here is that interfaces are essentially paired
+ with a particular plugin package. If plugins are installed in a
+ different package than the one the code which relies on the
+ interface they provide, they will not be found when the
+ application goes to load them.
+
+
Plugin Caching
+
+
In the course of using the Twisted plugin system, you may
+ notice dropin.cache
files appearing at
+ various locations. These files are used to cache information
+ about what plugins are present in the directory which contains
+ them. At times, this cached information may become out of date.
+ Twisted uses the mtimes of various files involved in the plugin
+ system to determine when this cache may have become invalid.
+ Twisted will try to re-write the cache each time it tries to use
+ it but finds it out of date.
+
+
For a site-wide install, it may not (indeed, should not) be
+ possible for applications running as normal users to rewrite the
+ cache file. While these applications will still run and find
+ correct plugin information, they may run more slowly than they
+ would if the cache was up to date, and they may also report
+ exceptions if certain plugins have been removed but which the
+ cache still references. For these reasons, when installing or
+ removing software which provides Twisted plugins, the site
+ administrator should be sure the cache is regenerated.
+ Well-behaved package managers for such software should take this
+ task upon themselves, since it is trivially automatable. The
+ canonical way to regenerate the cache is to run the following
+ Python code:
+
+
1
+2
+
from twisted .plugin import IPlugin , getPlugins
+list (getPlugins (IPlugin ))
+
+
+
As mentioned, it is normal for exceptions to be raised
+ once here if plugins have been removed.
+
+
Further Reading
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/process.html b/doc/core/howto/process.html
new file mode 100644
index 0000000..ed04ab0
--- /dev/null
+++ b/doc/core/howto/process.html
@@ -0,0 +1,732 @@
+
+
+Twisted Documentation: Using Processes
+
+
+
+
+ Using Processes
+
+
+
+
+
Overview
+
+
Along with connection to servers across the internet, Twisted also
+connects to local processes with much the same API. The API is described in
+more detail in the documentation of:
+
+
+
+
Running Another Process
+
+
Processes are run through the reactor,
+using reactor.spawnProcess
. Pipes are created to the child process,
+and added to the reactor core so that the application will not block while
+sending data into or pulling data out of the new
+process. reactor.spawnProcess
requires two arguments,
+ processProtocol
and executable
, and optionally takes
+several more: args
, environment
,
+ path
, userID
, groupID
,
+ usePTY
, and childFDs
. Not all of these are
+available on Windows.
+
+
+
1
+2
+3
+4
+5
+6
+
from twisted .internet import reactor
+
+processProtocol = MyProcessProtocol ()
+reactor .spawnProcess (processProtocol , executable , args =[program , arg1 , arg2 ],
+ env ={'HOME' : os .environ ['HOME' ]}, path ,
+ uid , gid , usePTY , childFDs )
+
+
+
+
+ processProtocol
should be an instance of a subclass of
+ twisted.internet.protocol.ProcessProtocol
. The
+ interface is described below.
+
+ executable
is the full path of the program to run. It
+ will be connected to processProtocol.
+
+ args
is a list of command line arguments to be passed to
+ the process. args[0]
should be the name of the process.
+
+ env
is a dictionary containing the environment to pass
+ through to the process.
+
+ path
is the directory to run the process in. The child
+ will switch to the given directory just before starting the new program.
+ The default is to stay in the current directory.
+
+ uid
and gid
are the user ID and group ID to
+ run the subprocess as. Of course, changing identities will be more likely
+ to succeed if you start as root.
+
+ usePTY
specifies whether the child process should be run
+ with a pty, or if it should just get a pair of pipes. Whether a program
+ needs to be run with a PTY or not depends on the particulars of that
+ program. Often, programs which primarily interact with users via a terminal
+ do need a PTY.
+
+ childFDs
lets you specify how the child's file
+ descriptors should be set up. Each key is a file descriptor number (an
+ integer) as seen by the child. 0, 1, and 2 are usually stdin, stdout, and
+ stderr, but some programs may be instructed to use additional fds through
+ command-line arguments or environment variables. Each value is either an
+ integer specifying one of the parent's current file descriptors, the
+ string r which creates a pipe that the parent can read from, or the
+ string w which creates a pipe that the parent can write to. If
+ childFDs
is not provided, a default is used which creates the
+ usual stdin-writer, stdout-reader, and stderr-reader pipes.
+
+
+
+
args
and env
have empty default values, but
+many programs depend upon them to be set correctly. At the very least,
+ args[0]
should probably be the same as executable
.
+If you just provide os.environ
for env
, the child
+program will inherit the environment from the current process, which is
+usually the civilized thing to do (unless you want to explicitly clean the
+environment as a security precaution). The default is to give an empty
+env
to the child.
+
+
reactor.spawnProcess
returns an instance that
+implements
+IProcessTransport
.
+
+
+
Writing a ProcessProtocol
+
+
The ProcessProtocol you pass to spawnProcess
is your
+interaction with the process. It has a very similar signature to a regular
+Protocol, but it has several extra methods to deal with events specific to
+a process. In our example, we will interface with 'wc' to create a word count
+of user-given text. First, we'll start by importing the required modules, and
+writing the initialization for our ProcessProtocol.
+
+
1
+2
+3
+4
+5
+
from twisted .internet import protocol
+class WCProcessProtocol (protocol .ProcessProtocol ):
+
+ def __init__ (self , text ):
+ self .text = text
+
+
+
When the ProcessProtocol is connected to the protocol, it has the
+connectionMade method called. In our protocol, we will write our text to the
+standard input of our process and then close standard input, to let the
+process know we are done writing to it.
+
+
1
+2
+3
+4
+
...
+ def connectionMade (self ):
+ self .transport .write (self .text )
+ self .transport .closeStdin ()
+
+
+
At this point, the process has receieved the data, and it's time for us
+to read the results. Instead of being received in dataReceived
,
+data from standard output is received in outReceived
. This is
+to distinguish it from data on standard error.
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
...
+ def outReceived (self , data ):
+ fieldLength = len (data ) / 3
+ lines = int (data [:fieldLength ])
+ words = int (data [fieldLength :fieldLength *2 ])
+ chars = int (data [fieldLength *2 :])
+ self .transport .loseConnection ()
+ self .receiveCounts (lines , words , chars )
+
+
+
Now, the process has parsed the output, and ended the connection to the
+process. Then it sends the results on to the final method, receiveCounts.
+This is for users of the class to override, so as to do other things with
+the data. For our demonstration, we will just print the results.
+
+
1
+2
+3
+4
+5
+6
+
...
+ def receiveCounts (self , lines , words , chars ):
+ print 'Received counts from wc.'
+ print 'Lines:' , lines
+ print 'Words:' , words
+ print 'Characters:' , chars
+
+
+
We're done! To use our WCProcessProtocol, we create an instance, and pass
+it to spawnProcess.
+
+
1
+2
+3
+4
+
from twisted .internet import reactor
+wcProcess = WCProcessProtocol ("accessing protocols through Twisted is fun!\n" )
+reactor .spawnProcess (wcProcess , 'wc' , ['wc' ])
+reactor .run ()
+
+
+
+
Things that can happen to your ProcessProtocol
+
+
These are the methods that you can usefully override in your subclass of
+ ProcessProtocol
:
+
+
+
+ .connectionMade()
: This is called when the program is
+ started, and makes a good place to write data into the stdin pipe (using
+ self.transport.write
).
+
+ .outReceived(data)
: This is called with data that was
+ received from the process' stdout pipe. Pipes tend to provide data in
+ larger chunks than sockets (one kilobyte is a common buffer size), so you
+ may not experience the random dribs and drabs behavior typical of
+ network sockets, but regardless you should be prepared to deal if you
+ don't get all your data in a single call. To do it properly,
+ outReceived
ought to simply accumulate the data and put off
+ doing anything with it until the process has finished.
+
+ .errReceived(data)
: This is called with data from the
+ process' stderr pipe. It behaves just like outReceived
.
+
+ .inConnectionLost
: This is called when the reactor notices
+ that the process' stdin pipe has closed. Programs don't typically close
+ their own stdin, so this will probably get called when your
+ ProcessProtocol has shut down the write side with self.transport.loseConnection
.
+
+ .outConnectionLost
: This is called when the program closes
+ its stdout pipe. This usually happens when the program terminates.
+
+ .errConnectionLost
: Same as
+ outConnectionLost
, but for stderr instead of stdout.
+
+ .processExited(status)
: This is called when the child
+ process has been reaped, and receives information about the process' exit
+ status. The status is passed in the form of a Failure
instance, created with a
+ .value
that either holds a ProcessDone
object if the process
+ terminated normally (it died of natural causes instead of receiving a
+ signal, and if the exit code was 0), or a ProcessTerminated
object (with an
+ .exitCode
attribute) if something went wrong.
+
+ .processEnded(status)
: This is called when all the file
+ descriptors associated with the child process have been closed and the
+ process has been reaped. This means it is the last callback which will be
+ made onto a ProcessProtocol
. The status
parameter
+ has the same meaning as it does for processExited
.
+
+
+
+
The base-class definitions of most of these functions are no-ops. This will
+result in all stdout and stderr being thrown away. Note that it is important
+for data you don't care about to be thrown away: if the pipe were not read,
+the child process would eventually block as it tried to write to a full
+pipe.
+
+
+
Things you can do from your ProcessProtocol
+
+
The following are the basic ways to control the child process:
+
+
+
+ self.transport.write(data)
: Stuff some data in the stdin
+ pipe. Note that this write
method will queue any data that can't
+ be written immediately. Writing will resume in the future when the pipe
+ becomes writable again.
+
+ self.transport.closeStdin
: Close the stdin pipe. Programs
+ which act as filters (reading from stdin, modifying the data, writing to
+ stdout) usually take this as a sign that they should finish their job and
+ terminate. For these programs, it is important to close stdin when you're
+ done with it, otherwise the child process will never quit.
+
+ self.transport.closeStdout
: Not usually called, since you're
+ putting the process into a state where any attempt to write to stdout will
+ cause a SIGPIPE error. This isn't a nice thing to do to the poor
+ process.
+
+ self.transport.closeStderr
: Not usually called, same reason
+ as closeStdout
.
+
+ self.transport.loseConnection
: Close all three pipes.
+
+ self.transport.signalProcess('KILL')
: Kill the child
+ process. This will eventually result in processEnded
being
+ called.
+
+
+
+
+
Verbose Example
+
+
Here is an example that is rather verbose about exactly when all the
+methods are called. It writes a number of lines into the wc
+program and then parses the output.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
+
+
+
+
+from twisted .internet import protocol
+from twisted .internet import reactor
+import re
+
+class MyPP (protocol .ProcessProtocol ):
+ def __init__ (self , verses ):
+ self .verses = verses
+ self .data = ""
+ def connectionMade (self ):
+ print "connectionMade!"
+ for i in range (self .verses ):
+ self .transport .write ("Aleph-null bottles of beer on the wall,\n" +
+ "Aleph-null bottles of beer,\n" +
+ "Take one down and pass it around,\n" +
+ "Aleph-null bottles of beer on the wall.\n" )
+ self .transport .closeStdin ()
+ def outReceived (self , data ):
+ print "outReceived! with %d bytes!" % len (data )
+ self .data = self .data + data
+ def errReceived (self , data ):
+ print "errReceived! with %d bytes!" % len (data )
+ def inConnectionLost (self ):
+ print "inConnectionLost! stdin is closed! (we probably did it)"
+ def outConnectionLost (self ):
+ print "outConnectionLost! The child closed their stdout!"
+
+
+ (dummy , lines , words , chars , file ) = re .split (r'\s+' , self .data )
+ print "I saw %s lines" % lines
+ def errConnectionLost (self ):
+ print "errConnectionLost! The child closed their stderr."
+ def processExited (self , reason ):
+ print "processExited, status %d" % (reason .value .exitCode ,)
+ def processEnded (self , reason ):
+ print "processEnded, status %d" % (reason .value .exitCode ,)
+ print "quitting"
+ reactor .stop ()
+
+pp = MyPP (10 )
+reactor .spawnProcess (pp , "wc" , ["wc" ], {})
+reactor .run ()
+
+
+
The exact output of this program depends upon the relative timing of some
+un-synchronized events. In particular, the program may observe the child
+process close its stderr pipe before or after it reads data from the stdout
+pipe. One possible transcript would look like this:
+
+
+% ./process.py
+connectionMade!
+inConnectionLost! stdin is closed! (we probably did it)
+errConnectionLost! The child closed their stderr.
+outReceived! with 24 bytes!
+outConnectionLost! The child closed their stdout!
+I saw 40 lines
+processEnded, status 0
+quitting
+Main loop terminated.
+%
+
+
+
Doing it the Easy Way
+
+
Frequently, one just needs a simple way to get all the output from a
+program. In the blocking world, you might use commands.getoutput
from the standard library, but
+using that in an event-driven program will cause everything else to stall
+until the command finishes. (in addition, the SIGCHLD handler used by that
+function does not play well with Twisted's own signal handling). For these
+cases, the twisted.internet.utils.getProcessOutput
+function can be used. Here is a simple example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
from twisted .internet import protocol , utils , reactor
+from twisted .python import failure
+from cStringIO import StringIO
+
+class FortuneQuoter (protocol .Protocol ):
+
+ fortune = '/usr/games/fortune'
+
+ def connectionMade (self ):
+ output = utils .getProcessOutput (self .fortune )
+ output .addCallbacks (self .writeResponse , self .noResponse )
+
+ def writeResponse (self , resp ):
+ self .transport .write (resp )
+ self .transport .loseConnection ()
+
+ def noResponse (self , err ):
+ self .transport .loseConnection ()
+
+
+if __name__ == '__main__' :
+ f = protocol .Factory ()
+ f .protocol = FortuneQuoter
+ reactor .listenTCP (10999 , f )
+ reactor .run ()
+
+
+
If you only need the final exit code (like commands.getstatusoutput(cmd)[0]
), the twisted.internet.utils.getProcessValue
function is
+useful. Here is an example:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+
from twisted .internet import utils , reactor
+
+def printTrueValue (val ):
+ print "/bin/true exits with rc=%d" % val
+ output = utils .getProcessValue ('/bin/false' )
+ output .addCallback (printFalseValue )
+
+def printFalseValue (val ):
+ print "/bin/false exits with rc=%d" % val
+ reactor .stop ()
+
+output = utils .getProcessValue ('/bin/true' )
+output .addCallback (printTrueValue )
+reactor .run ()
+
+
+
Mapping File Descriptors
+
+
stdin , stdout , and stderr are just conventions.
+Programs which operate as filters generally accept input on fd0, write their
+output on fd1, and emit error messages on fd2. This is common enough that
+the standard C library provides macros like stdin to mean fd0, and
+shells interpret the pipe character | to mean redirect fd1 from
+one command into fd0 of the next command .
+
+
But these are just conventions, and programs are free to use additional
+file descriptors or even ignore the standard three entirely. The
+childFDs argument allows you to specify exactly what kind of files
+descriptors the child process should be given.
+
+
Each child FD can be put into one of three states:
+
+
+ Mapped to a parent FD: this causes the child's reads and writes to
+ come from or go to the same source/destination as the parent.
+
+ Feeding into a pipe which can be read by the parent.
+
+ Feeding from a pipe which the parent writes into.
+
+
+
Mapping the child FDs to the parent's is very commonly used to send the
+child's stderr output to the same place as the parent's. When you run a
+program from the shell, it will typically leave fds 0, 1, and 2 mapped to
+the shell's 0, 1, and 2, allowing you to see the child program's output on
+the same terminal you used to launch the child. Likewise, inetd will
+typically map both stdin and stdout to the network socket, and may map
+stderr to the same socket or to some kind of logging mechanism. This allows
+the child program to be implemented with no knowledge of the network: it
+merely speaks its protocol by doing reads on fd0 and writes on fd1.
+
+
Feeding into a parent's read pipe is used to gather output from the
+child, and is by far the most common way of interacting with child
+processes.
+
+
Feeding from a parent's write pipe allows the parent to control the
+child. Programs like bc or ftp can be controlled this way, by
+writing commands into their stdin stream.
+
+
The childFDs dictionary maps file descriptor numbers (as will be
+seen by the child process) to one of these three states. To map the fd to
+one of the parent's fds, simply provide the fd number as the value. To map
+it to a read pipe, use the string r as the value. To map it to a
+write pipe, use the string w .
+
+
For example, the default mapping sets up the standard stdin/stdout/stderr
+pipes. It is implemented with the following dictionary:
+
+
1
+
childFDs = { 0 : "w" , 1 : "r" , 2 : "r" }
+
+
+
To launch a process which reads and writes to the same places that the
+parent python program does, use this:
+
+
1
+
childFDs = { 0 : 0 , 1 : 1 , 2 : 2 }
+
+
+
To write into an additional fd (say it is fd number 4), use this:
+
+
1
+
childFDs = { 0 : "w" , 1 : "r" , 2 : "r" , 4 : "w" }
+
+
+
+
+
ProcessProtocols with extra file descriptors
+
+
When you provide a childFDs dictionary with more than the normal
+three fds, you need addtional methods to access those pipes. These methods
+are more generalized than the .outReceived
ones described above.
+In fact, those methods (outReceived
and
+ errReceived
) are actually just wrappers left in for
+compatibility with older code, written before this generalized fd mapping was
+implemented. The new list of things that can happen to your ProcessProtocol
+is as follows:
+
+
+
+ .connectionMade
: This is called when the program is
+ started.
+
+ .childDataReceived(childFD, data)
: This is called with
+ data that was received from one of the process' output pipes (i.e. where
+ the childFDs value was r . The actual file number (from the point of
+ view of the child process) is in childFD . For compatibility, the
+ default implementation of .childDataReceived
dispatches to
+ .outReceived
or .errReceived
when childFD
+ is 1 or 2.
+
+ .childConnectionLost(childFD)
: This is called when the
+ reactor notices that one of the process' pipes has been closed. This
+ either means you have just closed down the parent's end of the pipe (with
+ .transport.closeChildFD
), the child closed the pipe
+ explicitly (sometimes to indicate EOF), or the child process has
+ terminated and the kernel has closed all of its pipes. The childFD
+ argument tells you which pipe was closed. Note that you can only find out
+ about file descriptors which were mapped to pipes: when they are mapped to
+ existing fds the parent has no way to notice when they've been closed. For
+ compatibility, the default implementation dispatches to
+ .inConnectionLost
, .outConnectionLost
, or
+ .errConnectionLost
.
+
+ .processEnded(status)
: This is called when the child
+ process has been reaped, and all pipes have been closed. This insures that
+ all data written by the child prior to its death will be received before
+ .processEnded
is invoked.
+
+
+
+
+
In addition to those methods, there are other methods available to
+influence the child process:
+
+
+
+ self.transport.writeToChild(childFD, data)
: Stuff some
+ data into an input pipe. .write
simply writes to
+ childFD=0.
+
+ self.transport.closeChildFD(childFD)
: Close one of the
+ child's pipes. Closing an input pipe is a common way to indicate EOF to
+ the child process. Closing an output pipe is neither very friendly nor
+ very useful.
+
+
+
Examples
+
+
GnuPG, the encryption program, can use additional file descriptors to
+accept a passphrase and emit status output. These are distinct from stdin
+(used to accept the crypttext), stdout (used to emit the plaintext), and
+stderr (used to emit human-readable status/warning messages). The passphrase
+FD reads until the pipe is closed and uses the resulting string to unlock
+the secret key that performs the actual decryption. The status FD emits
+machine-parseable status messages to indicate the validity of the signature,
+which key the message was encrypted to, etc.
+
+
gpg accepts command-line arguments to specify what these fds are, and
+then assumes that they have been opened by the parent before the gpg process
+is started. It simply performs reads and writes to these fd numbers.
+
+
To invoke gpg in decryption/verification mode, you would do something
+like the following:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
class GPGProtocol (ProcessProtocol ):
+ def __init__ (self , crypttext ):
+ self .crypttext = crypttext
+ self .plaintext = ""
+ self .status = ""
+ def connectionMade (self ):
+ self .transport .writeToChild (3 , self .passphrase )
+ self .transport .closeChildFD (3 )
+ self .transport .writeToChild (0 , self .crypttext )
+ self .transport .closeChildFD (0 )
+ def childDataReceived (self , childFD , data ):
+ if childFD == 1 : self .plaintext += data
+ if childFD == 4 : self .status += data
+ def processEnded (self , status ):
+ rc = status .value .exitCode
+ if rc == 0 :
+ self .deferred .callback (self )
+ else :
+ self .deferred .errback (rc )
+
+def decrypt (crypttext ):
+ gp = GPGProtocol (crypttext )
+ gp .deferred = Deferred ()
+ cmd = ["gpg" , "--decrypt" , "--passphrase-fd" , "3" , "--status-fd" , "4" ,
+ "--batch" ]
+ p = reactor .spawnProcess (gp , cmd [0 ], cmd , env =None ,
+ childFDs ={0 :"w" , 1 :"r" , 2 :2 , 3 :"w" , 4 :"r" })
+ return gp .deferred
+
+
+
In this example, the status output could be parsed after the fact. It
+could, of course, be parsed on the fly, as it is a simple line-oriented
+protocol. Methods from LineReceiver could be mixed in to make this parsing
+more convenient.
+
+
The stderr mapping (2:2 ) used will cause any GPG errors to be
+emitted by the parent program, just as if those errors had caused in the
+parent itself. This is sometimes desireable (it roughly corresponds to
+letting exceptions propagate upwards), especially if you do not expect to
+encounter errors in the child process and want them to be more visible to
+the end user. The alternative is to map stderr to a read-pipe and handle any
+such output from within the ProcessProtocol (roughly corresponding to
+catching the exception locally).
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/producers.html b/doc/core/howto/producers.html
new file mode 100644
index 0000000..764ab88
--- /dev/null
+++ b/doc/core/howto/producers.html
@@ -0,0 +1,90 @@
+
+
+Twisted Documentation: Producers and Consumers: Efficient High-Volume Streaming
+
+
+
+
+ Producers and Consumers: Efficient High-Volume Streaming
+
+
+
+
+
The purpose of this guide is to describe the Twisted producer and consumer system. The producer system allows applications to stream large amounts of data in a manner which is both memory and CPU efficient, and which does not introduce a source of unacceptable latency into the reactor.
+
+
Readers should have at least a passing familiarity with the terminology associated with interfaces.
+
+
Push Producers
+
+
A push producer is one which will continue to generate data without external prompting until told to stop; a pull producer will generate one chunk of data at a time in response to an explicit request for more data.
+
+
The push producer API is defined by the IPushProducer
interface. It is best to create a push producer when data generation is closedly tied to an event source. For example, a proxy which forwards incoming bytes from one socket to another outgoing socket might be implemented using a push producer: the dataReceived
takes the role of an event source from which the producer generates bytes, and requires no external intervention in order to do so.
+
+
There are three methods which may be invoked on a push producer at various points in its lifetime: pauseProducing
, resumeProducing
, and stopProducing
.
+
+
pauseProducing()
+
+
In order to avoid the possibility of using an unbounded amount of memory to buffer produced data which cannot be processed quickly enough, it is necessary to be able to tell a push producer to stop producing data for a while. This is done using the pauseProducing
method. Implementers of a push producer should temporarily stop producing data when this method is invoked.
+
+
resumeProducing()
+
+
After a push producer has been paused for some time, the excess of data which it produced will have been processed and the producer may again begin producing data. When the time for this comes, the push producer will have resumeProducing
invoked on it.
+
+
stopProducing()
+
+
Most producers will generate some finite (albeit, perhaps, unknown in advance) amount of data and then stop, having served their intended purpose. However, it is possible that before this happens an event will occur which renders the remaining, unproduced data irrelevant. In these cases, producing it anyway would be wasteful. The stopProducing
method will be invoked on the push producer. The implementation should stop producing data and clean up any resources owned by the producer.
+
+
Pull Producers
+
+
The pull producer API is defined by the IPullProducer
interface. Pull producers are useful in cases where there is no clear event source involved with the generation of data. For example, if the data is the result of some algorithmic process that is bound only by CPU time, a pull producer is appropriate.
+
+
Pull producers are defined in terms of only two methods: resumeProducing
and stopProducing
.
+
+
resumeProducing()
+
+
Unlike push producers, a pull producer is expected to only produce data in response to resumeProducing
being called. This method will be called whenever more data is required. How much data to produce in response to this method call depends on various factors: too little data and runtime costs will be dominated by the back-and-forth event notification associated with a buffer becoming empty and requesting more data to process; too much data and memory usage will be driven higher than it needs to be and the latency associated with creating so much data will cause overall performance in the application to suffer. A good rule of thumb is to generate between 16 and 64 kilobytes of data at a time, but you should experiment with various values to determine what is best for your application.
+
+
stopProducing()
+
+
This method has the same meaning for pull producers as it does for push producers.
+
+
Consumers
+
+
This far, I've discussed the various external APIs of the two kinds of producers supported by Twisted. However, I have not mentioned where the data a producer generates actually goes, nor what entity is responsible for invoking these APIs. Both of these roles are filled by consumers . Consumers are defined by the two interfaces IConsumer
and IFinishableConsumer
.
+
+
The slightly simpler of these two interfaces, IConsumer
, defines three methods: registerProducer
, unregisterProducer
, and write
. IFinishableConsumer
adds finish
.
+
+
registerProducer(producer, streaming)
+
+
So that a consumer can invoke methods on a producer, the consumer needs to be told about the producer. This is done with the registerProducer
method. The first argument is either a IPullProducer
or IPushProducer
provider; the second argument indicates which of these interfaces is provided: True
for push producers, False
for pull producers.
+
+
unregisterProducer()
+
+
Eventually a consumer will not longer be interested in a producer. This could be because the producer has finished generating all its data, or because the consumer is moving on to something else, or any number of other reasons. In any case, this method reverses the effects of registerProducer
.
+
+
write(data)
+
+
As you might guess, this is the method which a producer calls when it has generated some data. Push producers should call it as frequently as they like as long as they are not paused. Pull producers should call it once for each time resumeProducing
is called on them.
+
+
finish()
+
+
This method of IFinishableConsumer
s gives producers a way to explicitly notify the consumer that they have generated all the data they will ever generate.
+
+
Further Reading
+
+
An example push producer application can be found in doc/examples/streaming.py
.
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/quotes.html b/doc/core/howto/quotes.html
new file mode 100644
index 0000000..e72936c
--- /dev/null
+++ b/doc/core/howto/quotes.html
@@ -0,0 +1,214 @@
+
+
+Twisted Documentation: Setting up the TwistedQuotes application
+
+
+
+
+ Setting up the TwistedQuotes application
+
+
+
+
+
+
Goal
+
+
This document describes how to set up the TwistedQuotes application used in
+a number of other documents, such as designing Twisted applications .
+
+
Setting up the TwistedQuotes project directory
+
+
In order to run the Twisted Quotes example, you will need to do the
+following:
+
+
+Make a TwistedQuotes
directory on your system
+Place the following files in the TwistedQuotes
directory:
+
+ 1
+2
+3
+
"""
+Twisted Quotes
+"""
+ (this
+ file marks it as a package, see this section of the Python tutorial for more on packages)
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
from random import choice
+
+from zope .interface import implements
+
+from TwistedQuotes import quoteproto
+
+
+
+class StaticQuoter :
+ """
+ Return a static quote.
+ """
+
+ implements (quoteproto .IQuoter )
+
+ def __init__ (self , quote ):
+ self .quote = quote
+
+
+ def getQuote (self ):
+ return self .quote
+
+
+
+class FortuneQuoter :
+ """
+ Load quotes from a fortune-format file.
+ """
+ implements (quoteproto .IQuoter )
+
+ def __init__ (self , filenames ):
+ self .filenames = filenames
+
+
+ def getQuote (self ):
+ quoteFile = file (choice (self .filenames ))
+ quotes = quoteFile .read ().split ('\n%\n' )
+ quoteFile .close ()
+ return choice (quotes )
+
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
from zope .interface import Interface
+
+from twisted .internet .protocol import Factory , Protocol
+
+
+
+class IQuoter (Interface ):
+ """
+ An object that returns quotes.
+ """
+ def getQuote ():
+ """
+ Return a quote.
+ """
+
+
+
+class QOTD (Protocol ):
+ def connectionMade (self ):
+ self .transport .write (self .factory .quoter .getQuote ()+'\r\n' )
+ self .transport .loseConnection ()
+
+
+
+class QOTDFactory (Factory ):
+ """
+ A factory for the Quote of the Day protocol.
+
+ @type quoter: L{IQuoter} provider
+ @ivar quoter: An object which provides L{IQuoter} which will be used by
+ the L{QOTD} protocol to get quotes to emit.
+ """
+ protocol = QOTD
+
+ def __init__ (self , quoter ):
+ self .quoter = quoter
+
+
+
+Add the TwistedQuotes
directory's parent to your Python
+path. For example, if the TwistedQuotes directory's path is
+ /mystuff/TwistedQuotes
or c:\mystuff\TwistedQuotes
+add /mystuff
to your Python path. On UNIX this would be export PYTHONPATH=/mystuff:$PYTHONPATH
, on Microsoft
+Windows change the PYTHONPATH
variable through the
+Systems Properties dialog by adding ;c:\mystuff
at the
+end.
+
+Test your package by trying to import it in the Python interpreter:
+
+Python 2.1.3 (#1, Apr 20 2002, 22:45:31)
+[GCC 2.95.4 20011002 (Debian prerelease)] on linux2
+Type "copyright", "credits" or "license" for more information.
+>>> import TwistedQuotes
+>>> # No traceback means you're fine.
+
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/rdbms.html b/doc/core/howto/rdbms.html
new file mode 100644
index 0000000..b1c053d
--- /dev/null
+++ b/doc/core/howto/rdbms.html
@@ -0,0 +1,228 @@
+
+
+Twisted Documentation: twisted.enterprise.adbapi: Twisted RDBMS support
+
+
+
+
+ twisted.enterprise.adbapi: Twisted RDBMS support
+
+
+
+
+
Abstract
+
+
Twisted is an asynchronous networking framework, but most
+ database API implementations unfortunately have blocking
+ interfaces -- for this reason, twisted.enterprise.adbapi
was created. It is
+ a non-blocking interface to the standardized DB-API 2.0 API,
+ which allows you to access a number of different RDBMSes.
+
+
What you should already know
+
+
+
+
Quick Overview
+
+
Twisted is an asynchronous framework. This means standard
+ database modules cannot be used directly, as they typically
+ work something like:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
+db = dbmodule .connect ('mydb' , 'andrew' , 'password' )
+
+
+
+cursor = db .cursor ()
+
+
+resultset = cursor .query ('SELECT * FROM table WHERE ...' )
+
+
+
+
Those delays are unacceptable when using an asynchronous
+ framework such as Twisted. For this reason, twisted provides
+ twisted.enterprise.adbapi
, an
+ asynchronous wrapper for any
+ DB-API 2.0 -compliant module.
+
+
enterprise.adbapi
will do
+ blocking
+ database operations in separate threads, which trigger
+ callbacks in the originating thread when they complete. In the
+ meantime, the original thread can continue doing normal work,
+ like servicing other requests.
+
+
How do I use adbapi?
+
+
Rather than creating a database connection directly, use the
+ adbapi.ConnectionPool
+ class to manage
+ a connections for you. This allows enterprise.adbapi
to use multiple
+ connections, one per thread. This is easy:
+
1
+2
+3
+
+from twisted .enterprise import adbapi
+dbpool = adbapi .ConnectionPool ("dbmodule" , 'mydb' , 'andrew' , 'password' )
+
+
+
Things to note about doing this:
+
+
+ There is no need to import dbmodule directly. You just
+ pass the name to adbapi.ConnectionPool
's constructor.
+
+ The parameters you would pass to dbmodule.connect are
+ passed as extra arguments to adbapi.ConnectionPool
's constructor.
+ Keyword parameters work as well.
+
+
+
Now we can do a database query:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
+def getAge (user ):
+ return dbpool .runQuery ("SELECT age FROM users WHERE name = ?" , user )
+
+def printResult (l ):
+ if l :
+ print l [0 ][0 ], "years old"
+ else :
+ print "No such user"
+
+getAge ("joe" ).addCallback (printResult )
+
+
+
This is straightforward, except perhaps for the return value
+ of getAge
. It returns a twisted.internet.defer.Deferred
, which allows
+ arbitrary callbacks to be called upon completion (or upon
+ failure). More documentation on Deferred is available here .
+
+
In addition to runQuery
, there is also runOperation
,
+ and runInteraction
that gets called with a callable (e.g. a function).
+ The function will be called in the thread with a twisted.enterprise.adbapi.Transaction
,
+ which basically mimics a DB-API cursor. In all cases a database transaction will be
+ commited after your database usage is finished, unless an exception is raised in
+ which case it will be rolled back.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
def _getAge (txn , user ):
+
+ txn .execute ("SELECT * FROM foo" )
+
+ txn .execute ("SELECT age FROM users WHERE name = ?" , user )
+ result = txn .fetchall ()
+ if result :
+ return result [0 ][0 ]
+ else :
+ return None
+
+def getAge (user ):
+ return dbpool .runInteraction (_getAge , user )
+
+def printResult (age ):
+ if age != None :
+ print age , "years old"
+ else :
+ print "No such user"
+
+getAge ("joe" ).addCallback (printResult )
+
+
+
Also worth noting is that these examples assumes that dbmodule
+ uses the qmarks paramstyle (see the DB-API specification). If
+ your dbmodule uses a different paramstyle (e.g. pyformat) then
+ use that. Twisted doesn't attempt to offer any sort of magic
+ paramater munging -- runQuery(query,
+ params, ...)
maps directly onto cursor.execute(query, params, ...)
.
+
+
Examples of various database adapters
+
+
Notice that the first argument is the module name you would
+ usually import and get connect(...)
+ from, and that following arguments are whatever arguments you'd
+ call connect(...)
with.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
from twisted .enterprise import adbapi
+
+
+cp = adbapi .ConnectionPool ("gadfly" , "test" , "/tmp/gadflyDB" )
+
+
+cp = adbapi .ConnectionPool ("pyPgSQL.PgSQL" , database ="test" )
+
+
+cp = adbapi .ConnectionPool ("MySQLdb" , db ="test" )
+
+
+
And that's it!
+
+
That's all you need to know to use a database from within
+ Twisted. You probably should read the adbapi module's
+ documentation to get an idea of the other functions it has, but
+ hopefully this document presents the core ideas.
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/reactor-basics.html b/doc/core/howto/reactor-basics.html
new file mode 100644
index 0000000..a750f78
--- /dev/null
+++ b/doc/core/howto/reactor-basics.html
@@ -0,0 +1,93 @@
+
+
+Twisted Documentation: Reactor Overview
+
+
+
+
+ Reactor Overview
+
+
+
+
+
+
+ This HOWTO introduces the Twisted reactor, describes the basics of the
+ reactor and links to the various reactor interfaces.
+
+
+
Reactor Basics
+
+
The reactor is the core of the event loop within Twisted -- the loop
+ which drives applications using Twisted. The event loop is a programming
+ construct that waits for and dispatches events or messages in a program.
+ It works by calling some internal or external "event provider", which
+ generally blocks until an event has arrived, and then calls the relevant
+ event handler ("dispatches the event"). The reactor provides basic
+ interfaces to a number of services, including network communications,
+ threading, and event dispatching.
+
+
+
+ For information about using the reactor and the Twisted event loop, see:
+
+
+
+
+
There are multiple implementations of the reactor, each
+ modified to provide better support for specialized features
+ over the default implementation. More information about these
+ and how to use a particular implementation is available via
+ Choosing a Reactor .
+
+
+
+ Twisted applications can use the interfaces in twisted.application.service
to configure and run the
+ application instead of using
+ boilerplate reactor code. See Using Application for an introduction to
+ Application.
+
+
+
Using the reactor object
+
+
You can get to the reactor
object using the following code:
+
+
1
+
from twisted .internet import reactor
+
+
+
The reactor usually implements a set of interfaces, but
+ depending on the chosen reactor and the platform, some of
+ the interfaces may not be implemented:
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/sendmsg.html b/doc/core/howto/sendmsg.html
new file mode 100644
index 0000000..7570c26
--- /dev/null
+++ b/doc/core/howto/sendmsg.html
@@ -0,0 +1,221 @@
+
+
+Twisted Documentation: Extremely Low-Level Socket Operations
+
+
+
+
+ Extremely Low-Level Socket Operations
+
+
+
+
+
Introduction
+
+
+ Beyond supporting streams of data (SOCK_STREAM) or datagrams (SOCK_DGRAM),
+ POSIX sockets have additional features not accessible via send(2) and
+ recv(2). These features include things like scatter/gather I/O,
+ duplicating file descriptors into other processes, and accessing
+ out-of-band data.
+
+
+
+ Twisted includes a wrapper around the two C APIs which make these things
+ possible,
+ sendmsg
+ and
+ recvmsg .
+ This document covers their usage. It is intended for Twisted maintainers.
+ Application developers looking for this functionality should look for the
+ high-level APIs Twisted provides on top of these wrappers.
+
+
+
sendmsg
+
+
+ sendmsg(2)
exposes nearly all sender-side functionality of a
+ socket. For a SOCK_STREAM socket, it can send bytes that become part of
+ the stream of data being carried over the connection. For a SOCK_DGRAM
+ socket, it can send bytes that become datagrams sent from the socket. It
+ can send data from multiple memory locations (gather I/O). Over AF_UNIX
+ sockets, it can copy file descriptors into whichever process is receiving
+ on the other side. The wrapper included in Twisted,
+ send1msg
, exposes
+ many (but not all) of these features. This document covers the usage of
+ the features it does expose. The alternate spelling for the wrapper is
+ used to indicate the primary limitation, which is it that the interface
+ supports sending only one iovec at a time.
+
+
+
recvmsg
+
+
+ Likewise, recvmsg(2)
exposes nearly all the receiver-side
+ functionality of a socket. It can receive stream data over from a
+ SOCK_STREAM socket or datagrams from a SOCK_DGRAM socket. It can receive
+ that data into multiple memory locations (scatter I/O), and it can receive
+ those copied file descriptors. The wrapper included in
+ Twisted, recv1msg
,
+ exposes many (but not all) of these features. This document covers the
+ usage of the features it does expose. The alternate spelling for the
+ wrapper is used to indicate the primary limitation, which is that the
+ interface supports receiving only one iovec at a time.
+
+
+
Sending And Receiving Regular Data
+
+
+ sendmsg can be used in a way which makes it equivalent to using the send
+ call. The first argument to sendmsg is (in this case and all others) a
+ file descriptor over which to send the data. The second argument is a
+ string giving the data to send.
+
+
+
+ On the other end, recvmsg can be used to replace a recv call. The first
+ argument to recvmsg is (again, in all cases) a file descriptor over which
+ to receive the data. The second argument is an integer giving the maximum
+ number of data to receive.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
+
+
+"""
+Demonstration of sending bytes over a TCP connection using sendmsg.
+"""
+
+from socket import socketpair
+
+from twisted .python .sendmsg import send1msg , recv1msg
+
+def main ():
+ foo , bar = socketpair ()
+ sent = send1msg (foo .fileno (), "Hello, world" )
+ print "Sent" , sent , "bytes"
+ (received , flags , ancillary ) = recv1msg (bar .fileno (), 1024 )
+ print "Received" , repr (received )
+ print "Extra stuff, boring in this case" , flags , ancillary
+
+if __name__ == '__main__' :
+ main ()
+
+
+
Copying File Descriptors
+
+
+ Used with an AF_UNIX socket, sendmsg send a copy of a file descriptor into
+ whatever process is receiving on the other end of the socket. This is
+ done using the ancillary data argument. Ancillary data consists of a list
+ of three-tuples. A three-tuple constructed with SOL_SOCKET, SCM_RIGHTS,
+ and a platform-endian packed file descriptor number will copy that file
+ descriptor.
+
+
+
+ File descriptors copied this way must be received using a recvmsg call.
+ No special arguments are required to receive these descriptors. They will
+ appear, encoded as a native-order string, in the ancillary data list
+ returned by recvmsg.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
+
+
+"""
+Demonstration of copying a file descriptor over an AF_UNIX connection using
+sendmsg.
+"""
+
+from os import pipe , read , write
+from socket import SOL_SOCKET , socketpair
+from struct import unpack , pack
+
+from twisted .python .sendmsg import SCM_RIGHTS , send1msg , recv1msg
+
+def main ():
+ foo , bar = socketpair ()
+ reader , writer = pipe ()
+
+
+
+ sent = send1msg (
+ foo .fileno (), "\x00" , 0 ,
+ [(SOL_SOCKET , SCM_RIGHTS , pack ("i" , reader ))])
+
+
+ data , flags , ancillary = recv1msg (bar .fileno (), 1024 )
+ duplicate = unpack ("i" , ancillary [0 ][2 ])[0 ]
+
+
+ write (writer , "Hello, world" )
+ print "Read from original (%d): %r" % (reader , read (reader , 6 ))
+ print "Read from duplicate (%d): %r" % (duplicate , read (duplicate , 6 ))
+
+if __name__ == '__main__' :
+ main ()
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/servers.html b/doc/core/howto/servers.html
new file mode 100644
index 0000000..232a574
--- /dev/null
+++ b/doc/core/howto/servers.html
@@ -0,0 +1,548 @@
+
+
+Twisted Documentation: Writing Servers
+
+
+
+
+ Writing Servers
+
+
+
+
+
Overview
+
+
This document explains how you can use Twisted to implement
+ network protocol parsing and handling for TCP servers (the same
+ code can be reused for SSL and Unix socket servers). There is
+ a separate document covering UDP.
+
+
Your protocol handling class will usually subclass twisted.internet.protocol.Protocol
. Most
+ protocol handlers inherit either from this class or from one of
+ its convenience children. An instance of the protocol class
+ is instantiated per-connection, on demand, and will go
+ away when the connection is finished. This means that
+ persistent configuration is not saved in the
+ Protocol
.
+
+
The persistent configuration is kept in a Factory
+ class, which usually inherits
+ from twisted.internet.protocol.Factory
. The buildProtocol
+ method of the Factory
is used to create
+ a Protocol
for each new connection.
+
+
It is usually useful to be able to offer the same service on
+ multiple ports or network addresses. This is why
+ the Factory
does not listen to connections, and in
+ fact does not know anything about the
+ network. See the endpoints
+ documentation for more information,
+ or twisted.internet.interfaces.IReactorTCP.listenTCP
,
+ and the other IReactor*.listen*
APIs for the lower
+ level APIs that endpoints are based on.
+
+
This document will explain each step of the way.
+
+
Protocols
+
+
As mentioned above, this, along with auxiliary classes and
+ functions, is where most of the code is. A Twisted protocol
+ handles data in an asynchronous manner: the protocol responds
+ to events as they arrive from the network; the events arrive as
+ calls to methods on the protocol.
+
+
Here is a simple example:
+
1
+2
+3
+4
+5
+6
+
from twisted .internet .protocol import Protocol
+
+class Echo (Protocol ):
+
+ def dataReceived (self , data ):
+ self .transport .write (data )
+
+
+
This is one of the simplest protocols. It simply writes back
+ whatever is written to it, and does not respond to all events. Here is an
+ example of a Protocol responding to another event:
+
1
+2
+3
+4
+5
+6
+7
+
from twisted .internet .protocol import Protocol
+
+class QOTD (Protocol ):
+
+ def connectionMade (self ):
+ self .transport .write ("An apple a day keeps the doctor away\r\n" )
+ self .transport .loseConnection ()
+
+
+
This protocol responds to the initial connection with a well
+ known quote, and then terminates the connection.
+
+
The connectionMade event is usually where set up of the
+ connection object happens, as well as any initial greetings (as
+ in the QOTD protocol above, which is actually based on RFC
+ 865). The connectionLost
event is where tearing down of any
+ connection-specific objects is done. Here is an example:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
from twisted .internet .protocol import Protocol
+
+class Echo (Protocol ):
+
+ def __init__ (self , factory ):
+ self .factory = factory
+
+ def connectionMade (self ):
+ self .factory .numProtocols = self .factory .numProtocols +1
+ self .transport .write (
+ "Welcome! There are currently %d open connections.\n" %
+ (self .factory .numProtocols ,))
+
+ def connectionLost (self , reason ):
+ self .factory .numProtocols = self .factory .numProtocols -1
+
+ def dataReceived (self , data ):
+ self .transport .write (data )
+
+
+
Here connectionMade
and
+ connectionLost
cooperate to keep a count of the
+ active protocols in a shared object, the factory. The factory must
+ be passed to Echo.__init__
when creating a new
+ instance. The factory is used to share state that exists beyond the
+ lifetime of any given connection. You will see why this object is
+ called a "factory" in the next section.
+
+
loseConnection() and abortConnection()
+
+
In the code above, loseConnection
is called immediately
+ after writing to the transport. The loseConnection
call will
+ close the connection only when all the data has been written by Twisted
+ out to the operating system, so it is safe to use in this case without
+ worrying about transport writes being lost. If
+ a producer is being used with the
+ transport, loseConnection
will only close the connection once
+ the producer is unregistered.
+
+
In some cases, waiting until all the data is written out is not what we
+ want. Due to network failures, or bugs or maliciousness in the other side
+ of the connection, data written to the transport may not be deliverable,
+ and so even though loseConnection
was called the connection
+ will not be lost. In these cases, abortConnection
can be
+ used: it closes the connection immediately, regardless of buffered data
+ that is still unwritten in the transport, or producers that are still
+ registered. Note that abortConnection
is only available in
+ Twisted 11.1 and newer.
+
+
+
Using the Protocol
+
+
In this section, you will learn how to run a server which uses your
+ Protocol
.
+
+
Here is code that will run the QOTD server discussed
+ earlier:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
from twisted .internet .protocol import Factory
+from twisted .internet .endpoints import TCP4ServerEndpoint
+from twisted .internet import reactor
+
+class QOTDFactory (Factory ):
+ def buildProtocol (self , addr ):
+ return QOTD ()
+
+
+endpoint = TCP4ServerEndpoint (reactor , 8007 )
+endpoint .listen (QOTDFactory ())
+reactor .run ()
+
+
In this example, I create a protocol Factory
. I want to tell this
+ factory that its job is to build QOTD protocol instances, so I set its
+ buildProtocol
method to return instances of the QOTD class. Then, I want to listen
+ on a TCP port, so I make a TCP4ServerEndpoint
to identify the
+ port that I want to bind to, and then pass the factory I just created to
+ its listen
+ method.
+
+
Because this is a short example, nothing else has yet started up the
+ Twisted reactor. endpoint.listen
tells the reactor to handle
+ connections to the endpoint's address using a particular protocol, but the
+ reactor needs to be running in order for it to do anything.
+ reactor.run()
starts the reactor and then waits forever for
+ connections to arrive on the port you've specified.
+
+
You can stop the reactor by hitting Control-C in a terminal or calling
+ reactor.stop
.
+
+
For more information on different ways you can listen for incoming
+ connections, see the documentation for the
+ endpoints API .
+
+
Helper Protocols
+
+
Many protocols build upon similar lower-level abstraction.
+ The most popular in internet protocols is being line-based.
+ Lines are usually terminated with a CR-LF combinations.
+
+
However, quite a few protocols are mixed - they have
+ line-based sections and then raw data sections. Examples
+ include HTTP/1.1 and the Freenet protocol.
+
+
For those cases, there is the LineReceiver
+ protocol. This protocol dispatches to two different event
+ handlers - lineReceived
and
+ rawDataReceived
. By default, only
+ lineReceived
will be called, once for each line.
+ However, if setRawMode
is called, the protocol
+ will call rawDataReceived
until
+ setLineMode
is called, which returns it to using
+ lineReceived
. It also provides a method,
+ sendLine
, that writes data to the transport along
+ with the delimiter the class uses to split lines (by default,
+ \r\n
).
+
+
Here is an example for a simple use of the line
+ receiver:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .protocols .basic import LineReceiver
+
+class Answer (LineReceiver ):
+
+ answers = {'How are you?' : 'Fine' , None : "I don't know what you mean" }
+
+ def lineReceived (self , line ):
+ if self .answers .has_key (line ):
+ self .sendLine (self .answers [line ])
+ else :
+ self .sendLine (self .answers [None ])
+
+
+
Note that the delimiter is not part of the line.
+
+
Several other, less popular, helpers exist, such as a
+ netstring based protocol and a prefixed-message-length
+ protocol.
+
+
State Machines
+
+
Many Twisted protocol handlers need to write a state machine
+ to record the state they are at. Here are some pieces of advice
+ which help to write state machines:
+
+
+ Don't write big state machines. Prefer to write a state
+ machine which deals with one level of abstraction at a
+ time.
+
+ Don't mix application-specific code with Protocol
+ handling code. When the protocol handler has to make an
+ application-specific call, keep it as a method call.
+
+
+
Factories
+
+
Simpler Protocol Creation
+
+
For a factory which simply instantiates instances of a
+ specific protocol class, there is a simpler way to implement the factory.
+ The default implementation of the buildProtocol
method calls
+ the protocol
attribute of the factory to create
+ a Protocol
instance, and then sets an attribute on it
+ called factory
which points to the factory
+ itself. This lets every Protocol
access, and possibly
+ modify, the persistent configuration. Here is an example that uses these
+ features instead of overriding buildProtocol
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
from twisted .internet .protocol import Factory , Protocol
+from twisted .internet .endpoints import TCP4ServerEndpoint
+from twisted .internet import reactor
+
+class QOTD (Protocol ):
+
+ def connectionMade (self ):
+
+ self .transport .write (self .factory .quote + '\r\n' )
+ self .transport .loseConnection ()
+
+
+class QOTDFactory (Factory ):
+
+
+ protocol = QOTD
+
+ def __init__ (self , quote =None ):
+ self .quote = quote or 'An apple a day keeps the doctor away'
+
+endpoint = TCP4ServerEndpoint (reactor , 8007 )
+endpoint .listen (QOTDFactory ("configurable quote" ))
+reactor .run ()
+
+
+
Factory Startup and Shutdown
+
+
A Factory has two methods to perform application-specific
+ building up and tearing down (since a Factory is frequently
+ persisted, it is often not appropriate to do them in __init__
+ or __del__
, and would frequently be too early or too late).
+
+
Here is an example of a factory which allows its Protocols
+ to write to a special log-file:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
from twisted .internet .protocol import Factory
+from twisted .protocols .basic import LineReceiver
+
+
+class LoggingProtocol (LineReceiver ):
+
+ def lineReceived (self , line ):
+ self .factory .fp .write (line +'\n' )
+
+
+class LogfileFactory (Factory ):
+
+ protocol = LoggingProtocol
+
+ def __init__ (self , fileName ):
+ self .file = fileName
+
+ def startFactory (self ):
+ self .fp = open (self .file , 'a' )
+
+ def stopFactory (self ):
+ self .fp .close ()
+
+
+
Putting it All Together
+
+
As a final example, here's a simple chat server that allows
+ users to choose a username and then communicate with other
+ users. It demonstrates the use of shared state in the factory, a
+ state machine for each individual protocol, and communication
+ between different protocols.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
from twisted .internet .protocol import Factory
+from twisted .protocols .basic import LineReceiver
+from twisted .internet import reactor
+
+class Chat (LineReceiver ):
+
+ def __init__ (self , users ):
+ self .users = users
+ self .name = None
+ self .state = "GETNAME"
+
+ def connectionMade (self ):
+ self .sendLine ("What's your name?" )
+
+ def connectionLost (self , reason ):
+ if self .users .has_key (self .name ):
+ del self .users [self .name ]
+
+ def lineReceived (self , line ):
+ if self .state == "GETNAME" :
+ self .handle_GETNAME (line )
+ else :
+ self .handle_CHAT (line )
+
+ def handle_GETNAME (self , name ):
+ if self .users .has_key (name ):
+ self .sendLine ("Name taken, please choose another." )
+ return
+ self .sendLine ("Welcome, %s!" % (name ,))
+ self .name = name
+ self .users [name ] = self
+ self .state = "CHAT"
+
+ def handle_CHAT (self , message ):
+ message = "<%s> %s" % (self .name , message )
+ for name , protocol in self .users .iteritems ():
+ if protocol != self :
+ protocol .sendLine (message )
+
+
+class ChatFactory (Factory ):
+
+ def __init__ (self ):
+ self .users = {}
+
+ def buildProtocol (self , addr ):
+ return Chat (self .users )
+
+
+reactor .listenTCP (8123 , ChatFactory ())
+reactor .run ()
+
+
+
The only API you might not be familiar with
+ is listenTCP
. listenTCP
is
+ the method which connects a Factory
to the network.
+ This is the lower-level API
+ that endpoints wraps for you.
+
+
Here's a sample transcript of a chat session (this is text entered by the user):
+
+
+$ telnet 127.0.0.1 8123
+Trying 127.0.0.1...
+Connected to 127.0.0.1.
+Escape character is '^]'.
+What's your name?
+test
+Name taken, please choose another.
+bob
+Welcome, bob!
+hello
+<alice> hi bob
+twisted makes writing servers so easy!
+<alice> I couldn't agree more
+<carrol> yeah, it's great
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/ssl.html b/doc/core/howto/ssl.html
new file mode 100644
index 0000000..6744ad3
--- /dev/null
+++ b/doc/core/howto/ssl.html
@@ -0,0 +1,550 @@
+
+
+Twisted Documentation: Using SSL in Twisted
+
+
+
+
+ Using SSL in Twisted
+
+
+
+
+
Overview
+
+
This document describes how to use SSL in Twisted servers and clients. It
+ assumes that you know what SSL is, what some of the major reasons to use it
+ are, and how to generate your own SSL certificates, in particular self-signed
+ certificates. It also assumes that you are comfortable with creating TCP
+ servers and clients as described in the server howto
+ and client howto . After reading this
+ document you should be able to create servers and clients that can use SSL to
+ encrypt their connections, switch from using an unencrypted channel to an
+ encrypted one mid-connection, and require client authentication.
+
+
Using SSL in Twisted requires that you have
+ pyOpenSSL installed. A quick test to
+ verify that you do is to run from OpenSSL import SSL
at a
+ python prompt and not get an error.
+
+
SSL connections require SSL contexts. These contexts are generated by a
+ ContextFactory
that maintains state like the SSL method, private
+ key file name, and certificate file name.
+
+
Instead of using listenTCP and connectTCP to create a connection, use
+ listenSSL
and
+ connectSSL
for a
+ server and client respectively. These methods take a contextFactory as an
+ additional argument.
+
+
The basic server context factory is
+ twisted.internet.ssl.ContextFactory
, and the basic
+ client context factory is
+ twisted.internet.ssl.ClientContextFactory
. They can
+ be used as-is or subclassed.
+ twisted.internet.ssl.DefaultOpenSSLContextFactory
+ is a convenience server class that subclasses ContextFactory
+ and adds default parameters to the SSL handshake and connection. Another
+ useful class is
+ twisted.internet.ssl.CertificateOptions
; it is a
+ factory for SSL context objects that lets you specify many of the common
+ verification and session options so it can do the proper pyOpenSSL
+ initialization for you.
+
+
Those are the big immediate differences between TCP and SSL connections,
+ so let's look at an example. In it and all subsequent examples it is assumed
+ that keys and certificates for the server, certificate authority, and client
+ should they exist live in a keys/ subdirectory of the directory
+ containing the example code, and that the certificates are self-signed.
+
+
SSL echo server and client without client authentication
+
+
Authentication and encryption are two separate parts of the SSL protocol.
+ The server almost always needs a key and certificate to authenticate itself
+ to the client but is usually configured to allow encrypted connections with
+ unauthenticated clients who don't have certificates. This common case is
+ demonstrated first by adding SSL support to the echo client and server in
+ the core examples .
+
+
SSL echo server
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+
from twisted .internet import ssl , reactor
+from twisted .internet .protocol import Factory , Protocol
+
+class Echo (Protocol ):
+ def dataReceived (self , data ):
+ """As soon as any data is received, write it back."""
+ self .transport .write (data )
+
+if __name__ == '__main__' :
+ factory = Factory ()
+ factory .protocol = Echo
+ reactor .listenSSL (8000 , factory ,
+ ssl .DefaultOpenSSLContextFactory (
+ 'keys/server.key' , 'keys/server.crt' ))
+ reactor .run ()
+
+
+
SSL echo client
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
from twisted .internet import ssl , reactor
+from twisted .internet .protocol import ClientFactory , Protocol
+
+class EchoClient (Protocol ):
+ def connectionMade (self ):
+ print "hello, world"
+ self .transport .write ("hello, world!" )
+
+ def dataReceived (self , data ):
+ print "Server said:" , data
+ self .transport .loseConnection ()
+
+class EchoClientFactory (ClientFactory ):
+ protocol = EchoClient
+
+ def clientConnectionFailed (self , connector , reason ):
+ print "Connection failed - goodbye!"
+ reactor .stop ()
+
+ def clientConnectionLost (self , connector , reason ):
+ print "Connection lost - goodbye!"
+ reactor .stop ()
+
+if __name__ == '__main__' :
+ factory = EchoClientFactory ()
+ reactor .connectSSL ('localhost' , 8000 , factory , ssl .ClientContextFactory ())
+ reactor .run ()
+
+
+
Contexts are created according to a specified method.
+ SSLv3_METHOD
, SSLv23_METHOD
, and
+ TLSv1_METHOD
are the valid constants that represent SSL methods
+ to use when creating a context object. DefaultOpenSSLContextFactory
and
+ ClientContextFactory
default to using SSL.SSLv23_METHOD
as their
+ method, and it is compatible for communication with all the other methods
+ listed above. An older method constant, SSLv2_METHOD
, exists but
+ is explicitly disallowed in both DefaultOpenSSLContextFactory
and
+ ClientContextFactory
for being insecure by calling
+ set_options(SSL.OP_NO_SSLv2)
on their contexts. See
+ twisted.internet.ssl
for additional comments.
+
+
Using startTLS
+
+
If you want to switch from unencrypted to encrypted traffic
+ mid-connection, you'll need to turn on SSL with startTLS
on both
+ ends of the connection at the same time via some agreed-upon signal like the
+ reception of a particular message. You can readily verify the switch to an
+ encrypted channel by examining the packet payloads with a tool like
+ Wireshark .
+
+
startTLS server
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
from OpenSSL import SSL
+from twisted .internet import reactor , ssl
+from twisted .internet .protocol import ServerFactory
+from twisted .protocols .basic import LineReceiver
+
+class TLSServer (LineReceiver ):
+ def lineReceived (self , line ):
+ print "received: " + line
+
+ if line == "STARTTLS" :
+ print "-- Switching to TLS"
+ self .sendLine ('READY' )
+ ctx = ServerTLSContext (
+ privateKeyFileName ='keys/server.key' ,
+ certificateFileName ='keys/server.crt' ,
+ )
+ self .transport .startTLS (ctx , self .factory )
+
+
+class ServerTLSContext (ssl .DefaultOpenSSLContextFactory ):
+ def __init__ (self , *args , **kw ):
+ kw ['sslmethod' ] = SSL .TLSv1_METHOD
+ ssl .DefaultOpenSSLContextFactory .__init__ (self , *args , **kw )
+
+if __name__ == '__main__' :
+ factory = ServerFactory ()
+ factory .protocol = TLSServer
+ reactor .listenTCP (8000 , factory )
+ reactor .run ()
+
+
+
startTLS client
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
from OpenSSL import SSL
+from twisted .internet import reactor , ssl
+from twisted .internet .protocol import ClientFactory
+from twisted .protocols .basic import LineReceiver
+
+class ClientTLSContext (ssl .ClientContextFactory ):
+ isClient = 1
+ def getContext (self ):
+ return SSL .Context (SSL .TLSv1_METHOD )
+
+class TLSClient (LineReceiver ):
+ pretext = [
+ "first line" ,
+ "last thing before TLS starts" ,
+ "STARTTLS" ]
+
+ posttext = [
+ "first thing after TLS started" ,
+ "last thing ever" ]
+
+ def connectionMade (self ):
+ for l in self .pretext :
+ self .sendLine (l )
+
+ def lineReceived (self , line ):
+ print "received: " + line
+ if line == "READY" :
+ ctx = ClientTLSContext ()
+ self .transport .startTLS (ctx , self .factory )
+ for l in self .posttext :
+ self .sendLine (l )
+ self .transport .loseConnection ()
+
+class TLSClientFactory (ClientFactory ):
+ protocol = TLSClient
+
+ def clientConnectionFailed (self , connector , reason ):
+ print "connection failed: " , reason .getErrorMessage ()
+ reactor .stop ()
+
+ def clientConnectionLost (self , connector , reason ):
+ print "connection lost: " , reason .getErrorMessage ()
+ reactor .stop ()
+
+if __name__ == "__main__" :
+ factory = TLSClientFactory ()
+ reactor .connectTCP ('localhost' , 8000 , factory )
+ reactor .run ()
+
+
+
startTLS
is a transport method that gets passed a context.
+ It is invoked at an agreed-upon time in the data reception method of the
+ client and server protocols. The ServerTLSContext
and
+ ClientTLSContext
classes used above inherit from the basic
+ server and client context factories used in the earlier echo examples and
+ illustrate two more ways of setting an SSL method.
+
+
Client authentication
+
+
Server and client-side changes to require client authentication fall
+ largely under the dominion of pyOpenSSL, but few examples seem to exist on
+ the web so for completeness a sample server and client are provided here.
+
+
Client-authenticating server
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
from OpenSSL import SSL
+from twisted .internet import ssl , reactor
+from twisted .internet .protocol import Factory , Protocol
+
+class Echo (Protocol ):
+ def dataReceived (self , data ):
+ self .transport .write (data )
+
+def verifyCallback (connection , x509 , errnum , errdepth , ok ):
+ if not ok :
+ print 'invalid cert from subject:' , x509 .get_subject ()
+ return False
+ else :
+ print "Certs are fine"
+ return True
+
+if __name__ == '__main__' :
+ factory = Factory ()
+ factory .protocol = Echo
+
+ myContextFactory = ssl .DefaultOpenSSLContextFactory (
+ 'keys/server.key' , 'keys/server.crt'
+ )
+
+ ctx = myContextFactory .getContext ()
+
+ ctx .set_verify (
+ SSL .VERIFY_PEER | SSL .VERIFY_FAIL_IF_NO_PEER_CERT ,
+ verifyCallback
+ )
+
+
+
+ ctx .load_verify_locations ("keys/ca.pem" )
+
+ reactor .listenSSL (8000 , factory , myContextFactory )
+ reactor .run ()
+
+
+
Use the set_verify
method to set the verification mode for a
+ context object and the verification callback. The mode is either
+ VERIFY_NONE
or VERIFY_PEER
. If
+ VERIFY_PEER
is set, the mode can be augmented by
+ VERIFY_FAIL_IF_NO_PEER_CERT
and/or
+ VERIFY_CLIENT_ONCE
.
+
+
The callback takes as its arguments a connection object, X509 object,
+ error number, error depth, and return code. The purpose of the callback is
+ to allow you to enforce additional restrictions on the verification. Thus,
+ if the return code is False, you should return False; if the return code is
+ True and further verification passes, return True.
+
+
+
Client with certificates
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
from OpenSSL import SSL
+from twisted .internet import ssl , reactor
+from twisted .internet .protocol import ClientFactory , Protocol
+
+class EchoClient (Protocol ):
+ def connectionMade (self ):
+ print "hello, world"
+ self .transport .write ("hello, world!" )
+
+ def dataReceived (self , data ):
+ print "Server said:" , data
+ self .transport .loseConnection ()
+
+class EchoClientFactory (ClientFactory ):
+ protocol = EchoClient
+
+ def clientConnectionFailed (self , connector , reason ):
+ print "Connection failed - goodbye!"
+ reactor .stop ()
+
+ def clientConnectionLost (self , connector , reason ):
+ print "Connection lost - goodbye!"
+ reactor .stop ()
+
+class CtxFactory (ssl .ClientContextFactory ):
+ def getContext (self ):
+ self .method = SSL .SSLv23_METHOD
+ ctx = ssl .ClientContextFactory .getContext (self )
+ ctx .use_certificate_file ('keys/client.crt' )
+ ctx .use_privatekey_file ('keys/client.key' )
+
+ return ctx
+
+if __name__ == '__main__' :
+ factory = EchoClientFactory ()
+ reactor .connectSSL ('localhost' , 8000 , factory , CtxFactory ())
+ reactor .run ()
+
+
+
Other facilities
+
+
twisted.protocols.amp
supports encrypted
+ connections and exposes a startTLS
method one can use or
+ subclass. twisted.web
has built-in SSL support in
+ its client
, http
, and xmlrpc
modules.
+
+
Conclusion
+
+
After reading through this tutorial, you should be able to:
+
+ Use listenSSL
and connectSSL
to create servers and clients that use
+ SSL
+ Use startTLS
to switch a channel from being unencrypted to using SSL
+ mid-connection
+ Add server and client support for client authentication
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/stylesheet-unprocessed.css b/doc/core/howto/stylesheet-unprocessed.css
new file mode 100644
index 0000000..e4a62cc
--- /dev/null
+++ b/doc/core/howto/stylesheet-unprocessed.css
@@ -0,0 +1,20 @@
+
+span.footnote {
+ vertical-align: super;
+ font-size: small;
+}
+
+span.footnote:before
+{
+ content: "[Footnote: ";
+}
+
+span.footnote:after
+{
+ content: "]";
+}
+
+div.note:before
+{
+ content: "Note: ";
+}
diff --git a/doc/core/howto/stylesheet.css b/doc/core/howto/stylesheet.css
new file mode 100644
index 0000000..3c5961e
--- /dev/null
+++ b/doc/core/howto/stylesheet.css
@@ -0,0 +1,189 @@
+
+body
+{
+ margin-left: 2em;
+ margin-right: 2em;
+ border: 0px;
+ padding: 0px;
+ font-family: sans-serif;
+ }
+
+.done { color: #005500; background-color: #99ff99 }
+.notdone { color: #550000; background-color: #ff9999;}
+
+pre
+{
+ padding: 1em;
+ border: thin black solid;
+ line-height: 1.2em;
+}
+
+.boxed
+{
+ padding: 1em;
+ border: thin black solid;
+}
+
+.shell
+{
+ background-color: #ffffdd;
+}
+
+.python
+{
+ background-color: #dddddd;
+}
+
+.htmlsource
+{
+ background-color: #dddddd;
+}
+
+.py-prototype
+{
+ background-color: #ddddff;
+}
+
+
+.python-interpreter
+{
+ background-color: #ddddff;
+}
+
+.doit
+{
+ border: thin blue dashed ;
+ background-color: #0ef
+}
+
+.py-src-comment
+{
+ color: #1111CC
+}
+
+.py-src-keyword
+{
+ color: #3333CC;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-parameter
+{
+ color: #000066;
+ font-weight: bold;
+ line-height: 1.0em
+}
+
+.py-src-identifier
+{
+ color: #CC0000
+}
+
+.py-src-string
+{
+
+ color: #115511
+}
+
+.py-src-endmarker
+{
+ display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */
+}
+
+.py-linenumber
+{
+ background-color: #cdcdcd;
+ float: left;
+ margin-top: 0px;
+ width: 4.0em
+}
+
+.py-listing, .html-listing, .listing
+{
+ margin: 1ex;
+ border: thin solid black;
+ background-color: #eee;
+}
+
+.py-listing pre, .html-listing pre, .listing pre
+{
+ margin: 0px;
+ border: none;
+ border-bottom: thin solid black;
+}
+
+.py-listing .python
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.html-listing .htmlsource
+{
+ margin-top: 0;
+ margin-bottom: 0;
+ border: none;
+ border-bottom: thin solid black;
+ }
+
+.caption
+{
+ text-align: center;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.filename
+{
+ font-style: italic;
+ }
+
+.manhole-output
+{
+ color: blue;
+}
+
+hr
+{
+ display: inline;
+ }
+
+ul
+{
+ padding: 0px;
+ margin: 0px;
+ margin-left: 1em;
+ padding-left: 1em;
+ border-left: 1em;
+ }
+
+li
+{
+ padding: 2px;
+ }
+
+dt
+{
+ font-weight: bold;
+ margin-left: 1ex;
+ }
+
+dd
+{
+ margin-bottom: 1em;
+ }
+
+div.note
+{
+ background-color: #FFFFCC;
+ margin-top: 1ex;
+ margin-left: 5%;
+ margin-right: 5%;
+ padding-top: 1ex;
+ padding-left: 5%;
+ padding-right: 5%;
+ border: thin black solid;
+}
diff --git a/doc/core/howto/tap.html b/doc/core/howto/tap.html
new file mode 100644
index 0000000..1441cb5
--- /dev/null
+++ b/doc/core/howto/tap.html
@@ -0,0 +1,323 @@
+
+
+Twisted Documentation: Writing a twistd Plugin
+
+
+
+
+ Writing a twistd Plugin
+
+
+
+
+
This document describes adding subcommands to
+the twistd
command, as a way to facilitate the deployment
+of your applications. (This feature was added in Twisted 2.5)
+
+
The target audience of this document are those that have developed
+a Twisted application which needs a command line-based deployment
+mechanism.
+
+
There are a few prerequisites to understanding this document:
+
+ A basic understanding of the Twisted Plugin System (i.e.,
+ the twisted.plugin
module) is
+ necessary, however, step-by-step instructions will be
+ given. Reading The Twisted Plugin
+ System is recommended, in particular the Extending an
+ Existing Program section.
+ The Application infrastructure
+ is used in twistd
plugins; in particular, you should
+ know how to expose your program's functionality as a Service.
+ In order to parse command line arguments, the twistd
plugin
+ mechanism relies
+ on twisted.python.usage
, which is documented
+ in Using usage.Options .
+
+
+
Goals
+
+
After reading this document, the reader should be able to expose
+their Service-using application as a subcommand
+of twistd
, taking into consideration whatever was passed
+on the command line.
+
+
Alternatives to twistd plugins
+
The major alternative to the twistd plugin mechanism is the .tac
+file, which is a simple script to be used with the
+twistd -y/--python
parameter. The twistd plugin mechanism
+exists to offer a more extensible command-line-driven interface to
+your application. For more information on .tac
files, see
+the document Using the Twisted Application
+Framework .
+
+
+
Creating the plugin
+
+
The following directory structure is assumed of your project:
+
+
+ MyProject - Top level directory
+
+ myproject - Python package
+
+
+
+
+
+
+
+ During development of your project, Twisted plugins can be loaded
+ from a special directory in your project, assuming your top level
+ directory ends up in sys.path. Create a directory
+ named twisted
containing a directory
+ named plugins
, and add a file
+ named myproject_plugin.py
to it. This file will contain your
+ plugin. Note that you should not add any __init__.py files
+ to this directory structure, and the plugin file should not
+ be named myproject.py
(because that would conflict with
+ your project's module name).
+
+
+
+ In this file, define an object which provides the interfaces
+ twisted.plugin.IPlugin
+ and twisted.application.service.IServiceMaker
.
+
+
+
The tapname
attribute of your IServiceMaker provider
+will be used as the subcommand name in a command
+like twistd [subcommand] [args...]
, and
+the options
attribute (which should be
+a usage.Options
+subclass) will be used to parse the given args.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from zope .interface import implements
+
+from twisted .python import usage
+from twisted .plugin import IPlugin
+from twisted .application .service import IServiceMaker
+from twisted .application import internet
+
+from myproject import MyFactory
+
+
+class Options (usage .Options ):
+ optParameters = [["port" , "p" , 1235 , "The port number to listen on." ]]
+
+
+class MyServiceMaker (object ):
+ implements (IServiceMaker , IPlugin )
+ tapname = "myproject"
+ description = "Run this! It'll make your dog happy."
+ options = Options
+
+ def makeService (self , options ):
+ """
+ Construct a TCPServer from a factory defined in myproject.
+ """
+ return internet .TCPServer (int (options ["port" ]), MyFactory ())
+
+
+
+
+
+
+serviceMaker = MyServiceMaker ()
+
+
+
+ Now running twistd --help
should
+ print myproject
in the list of available subcommands,
+ followed by the description that we specified in the
+ plugin. twistd -n myproject
would,
+ assuming we defined a MyFactory
factory
+ inside myproject
, start a listening server on port 1235
+ with that factory.
+
+
+
Using cred with your TAP
+
+
+ Twisted ships with a robust authentication framework to use with
+ your application. If your server needs authentication functionality,
+ and you haven't read about twisted.cred
+ yet, read up on it first.
+
+
+
+ If you are building a twistd plugin and you want to support a wide
+ variety of authentication patterns, Twisted provides an easy-to-use
+ mixin for your Options subclass:
+ strcred.AuthOptionMixin
.
+ The following code is an example of using this mixin:
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
from twisted .cred import credentials , portal , strcred
+from twisted .python import usage
+from twisted .plugin import IPlugin
+from twisted .application .service import IServiceMaker
+from myserver import myservice
+
+class ServerOptions (usage .Options , strcred .AuthOptionMixin ):
+
+
+ supportedInterfaces = (credentials .IUsernamePassword ,)
+
+ optParameters = [
+ ["port" , "p" , 1234 , "Server port number" ],
+ ["host" , "h" , "localhost" , "Server hostname" ]]
+
+class MyServerServiceMaker (object ):
+ implements (IServiceMaker , IPlugin )
+ tapname = "myserver"
+ description = "This server does nothing productive."
+ options = ServerOptions
+
+ def makeService (self , options ):
+ """Construct a service object."""
+
+ realm = myservice .MyServerRealm (options ["host" ])
+
+
+
+
+ portal = portal .Portal (realm , options ["credCheckers" ])
+
+
+
+
+ interface = credentials .IUsernamePassword
+ portal = portal .Portal (realm , options ["credInterfaces" ][interface ])
+
+
+ factory = myservice .ServerFactory (realm , portal )
+
+
+ return internet .TCPServer (int (options ["port" ]), factory )
+
+
+
+
+
+serviceMaker = MyServerServiceMaker ()
+
+
+
+ Now that you have your TAP configured to support any authentication
+ we can throw at it, you're ready to use it. Here is an example of
+ starting your server using the /etc/passwd file for
+ authentication. (Clearly, this won't work on servers with shadow
+ passwords.)
+
+
+
+$ twistd myserver --auth passwd:/etc/passwd
+
+
+
+ For a full list of cred plugins supported, see twisted.plugins
, or use the command-line help:
+
+
+
+$ twistd myserver --help-auth
+$ twistd myserver --help-auth-type passwd
+
+
+
Conclusion
+
+
You should now be able to
+
+ Create a twistd plugin
+ Incorporate authentication into your plugin
+ Use it from your development environment
+ Install it correctly and use it in deployment
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/template.tpl b/doc/core/howto/template.tpl
new file mode 100644
index 0000000..1fbb517
--- /dev/null
+++ b/doc/core/howto/template.tpl
@@ -0,0 +1,23 @@
+
+
+
+
+
+Twisted Documentation:
+
+
+
+
+
+
+
+
+
+
+ Index
+ Version:
+
+
+
diff --git a/doc/core/howto/testing.html b/doc/core/howto/testing.html
new file mode 100644
index 0000000..01662b1
--- /dev/null
+++ b/doc/core/howto/testing.html
@@ -0,0 +1,167 @@
+
+
+Twisted Documentation: Writing tests for Twisted code using Trial
+
+
+
+
+ Writing tests for Twisted code using Trial
+
+
+
+
+
+
Trial basics
+
+
Trial is Twisted's testing framework. It provides a
+library for writing test cases and utility functions for working with the
+Twisted environment in your tests, and a command-line utility for running your
+tests. Trial is built on the Python standard library's unittest
+module.
+
+
To run all the Twisted tests, do:
+
+
+$ trial twisted
+
+
+
Refer to the Trial man page for other command-line options.
+
+
Trial directories
+
+
You might notice a new _trial_temp
folder in the
+current working directory after Trial completes the tests. This folder is the
+working directory for the Trial process. It can be used by unit tests and
+allows them to write whatever data they like to disk, and not worry
+about polluting the current working directory.
+
+
Folders named _trial_temp-<counter>
are
+created if two instances of Trial are run in parallel from the same directory,
+so as to avoid giving two different test-runs the same temporary directory.
+
+
The twisted.python.lockfile
utility is used to lock
+the _trial_temp
directories. On Linux, this results
+in symlinks to pids. On Windows, directories are created with a single file with
+a pid as the contents. These lock files will be cleaned up if Trial exits normally
+and otherwise they will be left behind. They should be cleaned up the next time
+Trial tries to use the directory they lock, but it's also safe to delete them
+manually if desired.
+
+
Twisted-specific quirks: reactor, Deferreds, callLater
+
+
The standard Python unittest
framework, from which Trial is
+derived, is ideal for testing code with a fairly linear flow of control.
+Twisted is an asynchronous networking framework which provides a clean,
+sensible way to establish functions that are run in response to events (like
+timers and incoming data), which creates a highly non-linear flow of control.
+Trial has a few extensions which help to test this kind of code. This section
+provides some hints on how to use these extensions and how to best structure
+your tests.
+
+
Leave the Reactor as you found it
+
+
Trial runs the entire test suite (over four thousand tests) in a single
+process, with a single reactor. Therefore it is important that your test
+leave the reactor in the same state as it found it. Leftover timers may
+expire during somebody else's unsuspecting test. Leftover connection attempts
+may complete (and fail) during a later test. These lead to intermittent
+failures that wander from test to test and are very time-consuming to track
+down.
+
+
If your test leaves event sources in the reactor, Trial will fail the test.
+The tearDown
method is a good place to put cleanup code: it is
+always run regardless of whether your test passes or fails (like a finally
+clause in a try-except-finally construct). Exceptions in tearDown
+are flagged as errors and flunk the test.
+ TestCase.addCleanup
is
+another useful tool for cleaning up. With it, you can register callables to
+clean up resources as the test allocates them. Generally, code should be
+written so that only resources allocated in the tests need to be cleaned up in
+the tests. Resources which are allocated internally by the implementation
+should be cleaned up by the implementation.
+
+
If your code uses Deferreds or depends on the reactor running, you can
+return a Deferred from your test method, setUp, or tearDown and Trial will
+do the right thing. That is, it will run the reactor for you until the
+Deferred has triggered and its callbacks have been run. Don't use
+ reactor.run()
, reactor.stop()
, reactor.crash()
or reactor.iterate()
in your tests.
+
+
Calls to reactor.callLater
create IDelayedCall
s. These need to be run
+or cancelled during a test, otherwise they will outlive the test. This would
+be bad, because they could interfere with a later test, causing confusing
+failures in unrelated tests! For this reason, Trial checks the reactor to make
+sure there are no leftover IDelayedCall
s in the reactor after a
+test, and will fail the test if there are. The cleanest and simplest way to
+make sure this all works is to return a Deferred from your test.
+
+
Similarly, sockets created during a test should be closed by the end of the
+test. This applies to both listening ports and client connections. So, calls
+to reactor.listenTCP
(and listenUNIX
, and so on)
+return IListeningPort
s, and these should be
+cleaned up before a test ends by calling their stopListening
method.
+Calls to reactor.connectTCP
return IConnector
s, which should be cleaned
+up by calling their disconnect
method. Trial
+will warn about unclosed sockets.
+
+
The golden rule is: If your tests call a function which returns a Deferred,
+your test should return a Deferred.
+
+
Using Timers to Detect Failing Tests
+
+
It is common for tests to establish some kind of fail-safe timeout that
+will terminate the test in case something unexpected has happened and none of
+the normal test-failure paths are followed. This timeout puts an upper bound
+on the time that a test can consume, and prevents the entire test suite from
+stalling because of a single test. This is especially important for the
+Twisted test suite, because it is run automatically by the buildbot whenever
+changes are committed to the Subversion repository.
+
+
The way to do this in Trial is to set the .timeout
attribute
+on your unit test method. Set the attribute to the number of seconds you wish
+to elapse before the test raises a timeout error. Trial has a default timeout
+which will be applied even if the timeout
attribute is not set.
+The Trial default timeout is usually sufficient and should be overridden only
+in unusual cases.
+
+
Interacting with warnings in tests
+
+
Trial includes specific support for interacting with Python's
+ warnings
module. This support allows warning-emitting code to
+be written test-driven, just as any other code would be. It also improves
+the way in which warnings reporting when a test suite is running.
+
+
TestCase.flushWarnings
+allows tests to be written which make assertions about what warnings have
+been emitted during a particular test method. In order to test a warning with
+ flushWarnings
, write a test which first invokes the code which
+will emit a warning and then calls flushWarnings
and makes
+assertions about the result. For example:
+
+
1
+2
+3
+4
+
class SomeWarningsTests (TestCase ):
+ def test_warning (self ):
+ warnings .warn ("foo is bad" )
+ self .assertEqual (len (self .flushWarnings ()), 1 )
+
+
+
Warnings emitted in tests which are not flushed will be included by the
+default reporter in its output after the result of the test. If Python's
+warnings filter system (see the
+-W command option to Python ) is configured to treat a warning as an error,
+then unflushed warnings will causes tests to fail and will be included in
+the summary section of the default reporter. Note that unlike usual
+operation, when warnings.warn
is called as part of a test
+method, it will not raise an exception when warnings have been configured as
+errors. However, if called outside of a test method (for example, at module
+scope in a test module or a module imported by a test module) then it
+ will raise an exception.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/threading.html b/doc/core/howto/threading.html
new file mode 100644
index 0000000..9eda7a4
--- /dev/null
+++ b/doc/core/howto/threading.html
@@ -0,0 +1,213 @@
+
+
+Twisted Documentation: Using Threads in Twisted
+
+
+
+
+ Using Threads in Twisted
+
+
+
+
+
Running code in a thread-safe manner
+
+
Most code in Twisted is not thread-safe. For example,
+ writing data to a transport from a protocol is not thread-safe.
+ Therefore, we want a way to schedule methods to be run in the
+ main event loop. This can be done using the function twisted.internet.interfaces.IReactorThreads.callFromThread
:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .internet import reactor
+
+def notThreadSafe (x ):
+ """do something that isn't thread-safe"""
+
+
+def threadSafeScheduler ():
+ """Run in thread-safe manner."""
+ reactor .callFromThread (notThreadSafe , 3 )
+
+reactor .run ()
+
+
+
Running code in threads
+
+
Sometimes we may want to run methods in threads - for
+ example, in order to access blocking APIs. Twisted provides
+ methods for doing so using the IReactorThreads API (twisted.internet.interfaces.IReactorThreads
).
+ Additional utility functions are provided in twisted.internet.threads
. Basically, these
+ methods allow us to queue methods to be run by a thread
+ pool.
+
+
For example, to run a method in a thread we can do:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
from twisted .internet import reactor
+
+def aSillyBlockingMethod (x ):
+ import time
+ time .sleep (2 )
+ print x
+
+
+reactor .callInThread (aSillyBlockingMethod , "2 seconds have passed" )
+reactor .run ()
+
+
+
Utility Methods
+
+
The utility methods are not part of the twisted.internet.reactor
APIs, but are implemented
+ in twisted.internet.threads
.
+
+
If we have multiple methods to run sequentially within a thread,
+ we can do:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+
from twisted .internet import reactor , threads
+
+def aSillyBlockingMethodOne (x ):
+ import time
+ time .sleep (2 )
+ print x
+
+def aSillyBlockingMethodTwo (x ):
+ print x
+
+
+commands = [(aSillyBlockingMethodOne , ["Calling First" ], {})]
+commands .append ((aSillyBlockingMethodTwo , ["And the second" ], {}))
+threads .callMultipleInThread (commands )
+reactor .run ()
+
+
+
For functions whose results we wish to get, we can have the
+ result returned as a Deferred:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
from twisted .internet import reactor , threads
+
+def doLongCalculation ():
+
+ return 3
+
+def printResult (x ):
+ print x
+
+
+d = threads .deferToThread (doLongCalculation )
+d .addCallback (printResult )
+reactor .run ()
+
+
+
If you wish to call a method in the reactor thread and get its result,
+ you can use blockingCallFromThread
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
from twisted .internet import threads , reactor , defer
+from twisted .web .client import getPage
+from twisted .web .error import Error
+
+def inThread ():
+ try :
+ result = threads .blockingCallFromThread (
+ reactor , getPage , "http://twistedmatrix.com/" )
+ except Error , exc :
+ print exc
+ else :
+ print result
+ reactor .callFromThread (reactor .stop )
+
+reactor .callInThread (inThread )
+reactor .run ()
+
+
+
blockingCallFromThread
will return the object or raise
+ the exception returned or raised by the function passed to it. If the
+ function passed to it returns a Deferred, it will return the value the
+ Deferred is called back with or raise the exception it is errbacked
+ with.
+
+
Managing the Thread Pool
+
+
The thread pool is implemented by twisted.python.threadpool.ThreadPool
.
+
+
We may want to modify the size of the threadpool, increasing
+ or decreasing the number of threads in use. We can do this
+ do this quite easily:
+
+
1
+2
+3
+
from twisted .internet import reactor
+
+reactor .suggestThreadPoolSize (30 )
+
+
+
The default size of the thread pool depends on the reactor being used;
+ the default reactor uses a minimum size of 5 and a maximum size of 10. Be
+ careful that you understand threads and their resource usage before
+ drastically altering the thread pool sizes.
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/time.html b/doc/core/howto/time.html
new file mode 100644
index 0000000..010f296
--- /dev/null
+++ b/doc/core/howto/time.html
@@ -0,0 +1,118 @@
+
+
+Twisted Documentation: Scheduling tasks for the future
+
+
+
+
+ Scheduling tasks for the future
+
+
+
+
+
Let's say we want to run a task X seconds in the future.
+ The way to do that is defined in the reactor interface twisted.internet.interfaces.IReactorTime
:
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
from twisted .internet import reactor
+
+def f (s ):
+ print "this will run 3.5 seconds after it was scheduled: %s" % s
+
+reactor .callLater (3.5 , f , "hello, world" )
+
+
+reactor .run ()
+
+
+
If the result of the function is important or if it may be necessary
+ to handle exceptions it raises, then the twisted.internet.task.deferLater
utility conveniently
+ takes care of creating a Deferred
and setting up a delayed
+ call:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
from twisted .internet import task
+from twisted .internet import reactor
+
+def f (s ):
+ return "This will run 3.5 seconds after it was scheduled: %s" % s
+
+d = task .deferLater (reactor , 3.5 , f , "hello, world" )
+def called (result ):
+ print result
+d .addCallback (called )
+
+
+reactor .run ()
+
+
+
If we want a task to run every X seconds repeatedly, we can
+ use twisted.internet.task.LoopingCall
:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .internet import task
+from twisted .internet import reactor
+
+def runEverySecond ():
+ print "a second has passed"
+
+l = task .LoopingCall (runEverySecond )
+l .start (1.0 )
+
+
+reactor .run ()
+
+
+
If we want to cancel a task that we've scheduled:
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .internet import reactor
+
+def f ():
+ print "I'll never run."
+
+callID = reactor .callLater (5 , f )
+callID .cancel ()
+reactor .run ()
+
+
+
As with all reactor-based code, in order for scheduling to work the reactor must be started using reactor.run()
.
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/trial.html b/doc/core/howto/trial.html
new file mode 100644
index 0000000..e52d0c0
--- /dev/null
+++ b/doc/core/howto/trial.html
@@ -0,0 +1,2042 @@
+
+
+Twisted Documentation: Test-driven development with Twisted
+
+
+
+
+ Test-driven development with Twisted
+
+
+
+
+
+
Writing good code is hard, or at least it can be. A major challenge is
+to ensure that your code remains correct as you add new functionality.
+
+
Unit testing is a
+modern, light-weight testing methodology in widespread use in many
+programming languages. Development that relies on unit tests is often
+referred to as Test-Driven Development
+(TDD ).
+Most Twisted code is tested using TDD.
+
+
To gain a solid understanding of unit testing in Python, you should read
+the unittest --
+Unit testing framework chapter of the Python Library
+Reference . There is also a ton of information available online and in
+books.
+
+
Introductory example of Python unit testing
+
+
This document is principally a guide to Trial, Twisted's unit testing
+framework. Trial is based on Python's unit testing framework. While we do not
+aim to give a comprehensive guide to general Python unit testing, it will be
+helpful to consider a simple non-networked example before expanding to cover a
+networking code that requires the special capabilities of Trial. If you are
+already familiar with unit test in Python, jump straight to the section
+specific to testing Twisted code .
+
+
Note: In what follows we will make a series of refinements
+to some simple classes. In order to keep the examples and source code links
+complete and to allow you to run Trial on the intermediate results at every
+stage, I add _N
(where the N
are successive
+integers) to file names to keep them separate. This is a minor visual
+distraction that should be ignored.
+
+
Creating an API and writing tests
+
+
We'll create a library for arithmetic calculation. First, create a
+project structure with a directory called calculus
containing an empty __init__.py
file.
+
+
Then put the following simple class definition API into calculus/base_1.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+
+
+
+
+class Calculation (object ):
+ def add (self , a , b ):
+ pass
+
+ def subtract (self , a , b ):
+ pass
+
+ def multiply (self , a , b ):
+ pass
+
+ def divide (self , a , b ):
+ pass
+
+
+
(Ignore the test-case-name
comment for
+now. You'll see why that's useful below .)
+
+
We've written the interface, but not the code. Now we'll write a set of
+tests. At this point of development, we'll be expecting all tests to
+fail. Don't worry, that's part of the point. Once we have a test framework
+functioning, and we have some decent tests written (and failing!), we'll go
+and do the actual development of our calculation API. This is the preferred
+way to work for many people using TDD - write tests first, make sure they
+fail, then do development. Others are not so strict and write tests after
+doing the development.
+
+
Create a test
directory beneath calculus
, with an empty __init__.py
file. In a calculus/test/test_base_1.py
, put the
+following:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
from calculus .base_1 import Calculation
+from twisted .trial import unittest
+
+class CalculationTestCase (unittest .TestCase ):
+ def test_add (self ):
+ calc = Calculation ()
+ result = calc .add (3 , 8 )
+ self .assertEqual (result , 11 )
+
+ def test_subtract (self ):
+ calc = Calculation ()
+ result = calc .subtract (7 , 3 )
+ self .assertEqual (result , 4 )
+
+ def test_multiply (self ):
+ calc = Calculation ()
+ result = calc .multiply (12 , 5 )
+ self .assertEqual (result , 60 )
+
+ def test_divide (self ):
+ calc = Calculation ()
+ result = calc .divide (12 , 5 )
+ self .assertEqual (result , 2 )
+
+
+
You should now have the following 4 files:
+
+
+ calculus/__init__.py
+ calculus/base_1.py
+ calculus/test/__init__.py
+ calculus/test/test_base_1.py
+
+
+
+
To run the tests, there are two things you must get set up. Make sure
+you get these both done - nothing below will work unless you do.
+
+
First, make sure that the directory that contains your
+ calculus
directory is in your Python load path. If you're
+using the Bash shell on some form of unix (e.g., Linux, Mac OS X), run
+ PYTHONPATH="$PYTHONPATH:`pwd`/.."
at
+the command line in the calculus
directory. Once you have your
+Python path set up correctly, you should be able to run Python from the
+command line and import calculus
without seeing
+an import error.
+
+
Second, make sure you can run the trial
+command. That is, make sure the directory containing the trial
+program on you system is in your shell's PATH
. The easiest way to check if you have this is to
+try running trial --help
at the command line. If
+you see a list of invocation options, you're in business. If your shell
+reports something like trial: command not found
,
+make sure you have Twisted installed properly, and that the Twisted
+ bin
directory is in your PATH
. If
+you don't know how to do this, get some local help, or figure it out by
+searching online for information on setting and changing environment
+variables for you operating system.
+
+
With those (one-time) preliminary steps out of the way, let's perform
+the tests. Run trial calculus.test.test_base_1
from the
+command line from the calculus
directory.
+
+You should see the following output (though your files are probably not in
+ /tmp
:
+
+
+$ trial calculus.test.test_base_1
+calculus.test.test_base_1
+ CalculationTestCase
+ test_add ... [FAIL]
+ test_divide ... [FAIL]
+ test_multiply ... [FAIL]
+ test_subtract ... [FAIL]
+
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File "/tmp/calculus/test/test_base_1.py", line 8, in test_add
+ self.assertEqual(result, 11)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 11
+
+
+calculus.test.test_base_1.CalculationTestCase.test_add
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File "/tmp/calculus/test/test_base_1.py", line 23, in test_divide
+ self.assertEqual(result, 2)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 2
+
+
+calculus.test.test_base_1.CalculationTestCase.test_divide
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File "/tmp/calculus/test/test_base_1.py", line 18, in test_multiply
+ self.assertEqual(result, 60)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 60
+
+
+calculus.test.test_base_1.CalculationTestCase.test_multiply
+===============================================================================
+[FAIL]
+Traceback (most recent call last):
+ File "/tmp/calculus/test/test_base_1.py", line 13, in test_subtract
+ self.assertEqual(result, 4)
+twisted.trial.unittest.FailTest: not equal:
+a = None
+b = 4
+
+
+calculus.test.test_base_1.CalculationTestCase.test_subtract
+-------------------------------------------------------------------------------
+Ran 4 tests in 0.042s
+
+FAILED (failures=4)
+
+
+
How to interpret this output? You get a list of the individual tests, each
+followed by its result. By default, failures are printed at the end, but this
+can be changed with the -e
(or --rterrors
) option.
+
+
One very useful thing in this output is the fully-qualified name of the
+failed tests. This appears at the bottom of each =-delimited area of the
+output. This allows you to copy and paste it to just run a single test you're
+interested in. In our example, you could run trial
+calculus.test.test_base_1.CalculationTestCase.test_subtract
from the
+shell.
+
+
Note that trial can use different reporters to modify its output. Run
+ trial --help-reporters
to see a list of
+reporters.
+
+
+The tests can be run by trial
in multiple ways:
+
+ trial calculus
: run all the tests for the
+ calculus package.
+
+ trial calculus.test
: run using Python's
+ import
notation.
+
+ trial calculus.test.test_base_1
: as above, for
+ a specific test module. You can follow that logic by putting your class name
+ and even a method name to only run those specific tests.
+
+ trial
+ --testmodule=calculus/base_1.py
: use the test-case-name
comment in the first line of
+ calculus/base_1.py
to find the tests.
+
+ trial calculus/test
: run all the tests in the
+ test directory (not recommended).
+
+ trial calculus/test/test_base_1.py
: run a
+ specific test file (not recommended).
+
+
+The first 3 versions using full qualified names are strongly encouraged: they
+are much more reliable and they allow you to easily be more selective in your
+test runs.
+
+
+
You'll notice that Trial create a _trial_temp
directory in
+the directory where you run the tests. This has a file called
+ test.log
which contains the log output of the tests (created
+using log.msg
or log.err
functions). Examine this file if you add
+logging to your tests.
+
+
Making the tests pass
+
+
Now that we have a working test framework in place, and our tests are
+failing (as expected) we can go and try to implement the correct API. We'll do
+that in a new version of the above base_1
+module, calculus/base_2.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+
+
+class Calculation (object ):
+ def add (self , a , b ):
+ return a + b
+
+ def subtract (self , a , b ):
+ return a - b
+
+ def multiply (self , a , b ):
+ return a * b
+
+ def divide (self , a , b ):
+ return a / b
+
+
+
We'll also create a new version of test_base_1 which imports and tests this
+new implementation,
+in calculus/test_base_2.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
from calculus .base_2 import Calculation
+from twisted .trial import unittest
+
+
+
+class CalculationTestCase (unittest .TestCase ):
+
+ def test_add (self ):
+ calc = Calculation ()
+ result = calc .add (3 , 8 )
+ self .assertEqual (result , 11 )
+
+
+ def test_subtract (self ):
+ calc = Calculation ()
+ result = calc .subtract (7 , 3 )
+ self .assertEqual (result , 4 )
+
+
+ def test_multiply (self ):
+ calc = Calculation ()
+ result = calc .multiply (12 , 5 )
+ self .assertEqual (result , 60 )
+
+
+ def test_divide (self ):
+ calc = Calculation ()
+ result = calc .divide (12 , 5 )
+ self .assertEqual (result , 2 )
+ is a copy of test_base_1, but with the import changed. Run
trial
again as above, and your tests should now pass:
+
+
+$ trial calculus.test.test_base_2
+
+Running 4 tests.
+calculus.test.test_base
+ CalculationTestCase
+ test_add ... [OK]
+ test_divide ... [OK]
+ test_multiply ... [OK]
+ test_subtract ... [OK]
+
+-------------------------------------------------------------------------------
+Ran 4 tests in 0.067s
+
+PASSED (successes=4)
+
+
+
Factoring out common test logic
+
+
You'll notice that our test file contains redundant code. Let's get rid
+of that. Python's unit testing framework allows your test class to define a
+ setUp
method that is called before
+ each test method in the class. This allows you to add attributes
+to self
that can be used in tests
+methods. We'll also add a parameterized test method to further simplify the
+code.
+
+
Note that a test class may also provide the counterpart of setUp
, named tearDown
,
+which will be called after each test (whether successful or
+not). tearDown
is mainly used for post-test
+cleanup purposes. We will not use tearDown
+until later.
+
+
Create calculus/test/test_base_2b.py
as
+follows:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
from calculus .base_2 import Calculation
+from twisted .trial import unittest
+
+
+
+class CalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ self .calc = Calculation ()
+
+
+ def _test (self , operation , a , b , expected ):
+ result = operation (a , b )
+ self .assertEqual (result , expected )
+
+
+ def test_add (self ):
+ self ._test (self .calc .add , 3 , 8 , 11 )
+
+
+ def test_subtract (self ):
+ self ._test (self .calc .subtract , 7 , 3 , 4 )
+
+
+ def test_multiply (self ):
+ self ._test (self .calc .multiply , 6 , 9 , 54 )
+
+
+ def test_divide (self ):
+ self ._test (self .calc .divide , 12 , 5 , 2 )
+
+
+
Much cleaner, no?
+
+
We'll now add some additional error tests. Testing just for successful
+use of the API is generally not enough, especially if you expect your code
+to be used by others. Let's make sure the Calculation
class raises exceptions if someone tries
+to call its methods with arguments that cannot be converted to
+integers.
+
+
We arrive at calculus/test/test_base_3.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
from calculus .base_3 import Calculation
+from twisted .trial import unittest
+
+
+
+class CalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ self .calc = Calculation ()
+
+
+ def _test (self , operation , a , b , expected ):
+ result = operation (a , b )
+ self .assertEqual (result , expected )
+
+
+ def _test_error (self , operation ):
+ self .assertRaises (TypeError , operation , "foo" , 2 )
+ self .assertRaises (TypeError , operation , "bar" , "egg" )
+ self .assertRaises (TypeError , operation , [3 ], [8 , 2 ])
+ self .assertRaises (TypeError , operation , {"e" : 3 }, {"r" : "t" })
+
+
+ def test_add (self ):
+ self ._test (self .calc .add , 3 , 8 , 11 )
+
+
+ def test_subtract (self ):
+ self ._test (self .calc .subtract , 7 , 3 , 4 )
+
+
+ def test_multiply (self ):
+ self ._test (self .calc .multiply , 6 , 9 , 54 )
+
+
+ def test_divide (self ):
+ self ._test (self .calc .divide , 12 , 5 , 2 )
+
+
+ def test_errorAdd (self ):
+ self ._test_error (self .calc .add )
+
+
+ def test_errorSubtract (self ):
+ self ._test_error (self .calc .subtract )
+
+
+ def test_errorMultiply (self ):
+ self ._test_error (self .calc .multiply )
+
+
+ def test_errorDivide (self ):
+ self ._test_error (self .calc .divide )
+
+
+
We've added four new tests and one general-purpose function, _test_error
. This function uses the assertRaises
method, which takes an exception class,
+a function to run and its arguments, and checks that calling the function
+on the arguments does indeed raise the given exception.
+
+
If you run the above, you'll see that not all tests fail. In Python it's
+often valid to add and multiply objects of different and even differing
+types, so the code in the add and mutiply tests does not raise an exception
+and those tests therefore fail. So let's add explicit type conversion to
+our API class. This brings us to calculus/base_3.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
+
+class Calculation (object ):
+ def _make_ints (self , *args ):
+ try :
+ return map (int , args )
+ except ValueError :
+ raise TypeError ("Couldn't coerce arguments to integers: %s" % args )
+
+ def add (self , a , b ):
+ a , b = self ._make_ints (a , b )
+ return a + b
+
+ def subtract (self , a , b ):
+ a , b = self ._make_ints (a , b )
+ return a - b
+
+ def multiply (self , a , b ):
+ a , b = self ._make_ints (a , b )
+ return a * b
+
+ def divide (self , a , b ):
+ a , b = self ._make_ints (a , b )
+ return a / b
+
+
+
Here the _make_ints
helper function tries to
+convert a list into a list of equivalent integers, and raises a TypeError
in case the conversion goes wrong.
+
+
Note: The int
conversion can also
+raise a TypeError
if passed something of the
+wrong type, such as a list. We'll just let that exception go by as TypeError
is already what we want in case something
+goes wrong.
+
+
+
+
Twisted specific testing
+
+
Up to this point we've been doing fairly standard Python unit testing.
+With only a few cosmetic changes (most importantly, directly importing
+ unittest
instead of using Twisted's unittest
version) we could make the
+above tests run using Python's standard library unit testing framework.
+
+
Here we will assume a basic familiarity with Twisted's network I/O, timing,
+and Deferred APIs. If you haven't already read them, you should read the
+documentation on Writing
+Servers , Writing Clients ,
+and Deferreds .
+
+
Now we'll get to the real point of this tutorial and take advantage of
+Trial to test Twisted code.
+
+
Testing a protocol
+
+
We'll now create a custom protocol to invoke our class from within a
+telnet-like session. We'll remotely call commands with arguments and read back
+the response. The goal will be to test our network code without creating
+sockets.
+
+
Creating and testing the server
+
+
First we'll write the tests, and then explain what they do. The first
+version of the remote test code is:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
from calculus .remote_1 import RemoteCalculationFactory
+from twisted .trial import unittest
+from twisted .test import proto_helpers
+
+
+
+class RemoteCalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ factory = RemoteCalculationFactory ()
+ self .proto = factory .buildProtocol (('127.0.0.1' , 0 ))
+ self .tr = proto_helpers .StringTransport ()
+ self .proto .makeConnection (self .tr )
+
+
+ def _test (self , operation , a , b , expected ):
+ self .proto .dataReceived ('%s %d %d\r\n' % (operation , a , b ))
+ self .assertEqual (int (self .tr .value ()), expected )
+
+
+ def test_add (self ):
+ return self ._test ('add' , 7 , 6 , 13 )
+
+
+ def test_subtract (self ):
+ return self ._test ('subtract' , 82 , 78 , 4 )
+
+
+ def test_multiply (self ):
+ return self ._test ('multiply' , 2 , 8 , 16 )
+
+
+ def test_divide (self ):
+ return self ._test ('divide' , 14 , 3 , 4 )
+
+
+
To fully understand this client, it helps a lot to be comfortable with
+the Factory/Protocol/Transport pattern used in Twisted.
+
+
We first create a protocol factory object. Note that we have yet to see
+the RemoteCalculationFactory
class. It is in
+ calculus/remote_1.py
below. We
+call buildProtocol
to ask the factory to build us a
+protocol object that knows how to talk to our server. We then make a fake
+network transport, an instance of twisted.test.proto_helpers.StringTransport
+class (note that test packages are generally not part of Twisted's public API;
+twisted.test.proto_helpers
is an exception). This fake
+transport is the key to the communications. It is used to emulate a network
+connection without a network. The address and port passed to buildProtocol
+are typically used by the factory to choose to immediately deny remote connections; since we're using a fake transport, we can choose any value that will be acceptable to the factory. In this case the factory just ignores the address, so we don't need to pick anything in particular.
+
+
Testing protocols without the use of real network connections is both simple and recommended when testing Twisted
+code. Even though there are many tests in Twisted that use the network,
+most good tests don't. The problem with unit tests and networking is that
+networks aren't reliable. We cannot know that they will exhibit reasonable
+behavior all the time. This creates intermittent test failures due to
+network vagaries. Right now we're trying to test our Twisted code, not
+network reliability. By setting up and using a fake transport, we can
+write 100% reliable tests. We can also test network failures in a deterministic manner, another important part of your complete test suite.
+
+
The final key to understanding this client code is the _test
method. The call to dataReceived
simulates data arriving on the network
+transport. But where does it arrive? It's handed to the lineReceived
method of the protocol instance (in
+ calculus/remote_1.py
below). So the client
+is essentially tricking the server into thinking it has received the
+operation and the arguments over the network. The server (once again, see
+below) hands the work off to its CalculationProxy
object which in turn hands it to its
+ Calculation
instance. The result is written
+back via sendLine
(into the fake string
+transport object), and is then immediately available to the client, who
+fetches it with tr.value()
and checks that it
+has the expected value. So there's quite a lot going on behind the scenes
+in the two-line _test
method above.
+
+
Finally , let's see the implementation of this protocol. Put the
+following into calculus/remote_1.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+
+
+from twisted .protocols import basic
+from twisted .internet import protocol
+from calculus .base_3 import Calculation
+
+
+
+class CalculationProxy (object ):
+ def __init__ (self ):
+ self .calc = Calculation ()
+ for m in ['add' , 'subtract' , 'multiply' , 'divide' ]:
+ setattr (self , 'remote_%s' % m , getattr (self .calc , m ))
+
+
+
+class RemoteCalculationProtocol (basic .LineReceiver ):
+ def __init__ (self ):
+ self .proxy = CalculationProxy ()
+
+
+ def lineReceived (self , line ):
+ op , a , b = line .split ()
+ a = int (a )
+ b = int (b )
+ op = getattr (self .proxy , 'remote_%s' % (op ,))
+ result = op (a , b )
+ self .sendLine (str (result ))
+
+
+
+class RemoteCalculationFactory (protocol .Factory ):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main ():
+ from twisted .internet import reactor
+ from twisted .python import log
+ import sys
+ log .startLogging (sys .stdout )
+ reactor .listenTCP (0 , RemoteCalculationFactory ())
+ reactor .run ()
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
As mentioned, this server creates a protocol that inherits from basic.LineReceiver
, and then a
+factory that uses it as protocol. The only trick is the CalculationProxy
object, which calls Calculation
methods through remote_*
methods. This pattern is used frequently in
+Twisted, because it is very explicit about what methods you are making
+accessible.
+
+
If you run this test (trial
+calculus.test.test_remote_1
), everything should be fine. You can also
+run a server to test it with a telnet client. To do that, call python calculus/remote_1.py
. You should have the following output:
+
+
+2008-04-25 10:53:27+0200 [-] Log opened.
+2008-04-25 10:53:27+0200 [-] __main__.RemoteCalculationFactory starting on 46194
+2008-04-25 10:53:27+0200 [-] Starting factory <__main__.RemoteCalculationFactory instance at 0x846a0cc>
+
+
+
46194 is replaced by a random port. You can then call telnet on it:
+
+$ telnet localhost 46194
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+add 4123 9423
+13546
+
+
+
It works!
+
+
Creating and testing the client
+
+
Of course, what we build is not particulary useful for now : we'll now build
+a client to our server, to be able to use it inside a Python program. And it
+will serve our next purpose.
+
+
Create calculus/test/test_client_1.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
from calculus .client_1 import RemoteCalculationClient
+from twisted .trial import unittest
+from twisted .test import proto_helpers
+
+
+
+class ClientCalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ self .tr = proto_helpers .StringTransport ()
+ self .proto = RemoteCalculationClient ()
+ self .proto .makeConnection (self .tr )
+
+
+ def _test (self , operation , a , b , expected ):
+ d = getattr (self .proto , operation )(a , b )
+ self .assertEqual (self .tr .value (), '%s %d %d\r\n' % (operation , a , b ))
+ self .tr .clear ()
+ d .addCallback (self .assertEqual , expected )
+ self .proto .dataReceived ("%d\r\n" % (expected ,))
+ return d
+
+
+ def test_add (self ):
+ return self ._test ('add' , 7 , 6 , 13 )
+
+
+ def test_subtract (self ):
+ return self ._test ('subtract' , 82 , 78 , 4 )
+
+
+ def test_multiply (self ):
+ return self ._test ('multiply' , 2 , 8 , 16 )
+
+
+ def test_divide (self ):
+ return self ._test ('divide' , 14 , 3 , 4 )
+
+
+
It's really symmetric to the server test cases. The only tricky part is
+that we don't use a client factory. We're lazy, and it's not very useful in
+the client part, so we instantiate the protocol directly.
+
+
Incidentally, we have introduced a very important concept here: the tests
+now return a Deferred object, and the assertion is done in a callback. The
+important thing to do here is to not forget to return the
+Deferred . If you do, your tests will pass even if nothing is asserted.
+That's also why it's important to make tests fail first: if your tests pass
+whereas you know they shouldn't, there is a problem in your tests.
+
+
We'll now add the remote client class to produce calculus/client_1.py
:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
+
+from twisted .protocols import basic
+from twisted .internet import defer
+
+
+
+class RemoteCalculationClient (basic .LineReceiver ):
+ def __init__ (self ):
+ self .results = []
+
+
+ def lineReceived (self , line ):
+ d = self .results .pop (0 )
+ d .callback (int (line ))
+
+
+ def _sendOperation (self , op , a , b ):
+ d = defer .Deferred ()
+ self .results .append (d )
+ line = "%s %d %d" % (op , a , b )
+ self .sendLine (line )
+ return d
+
+
+ def add (self , a , b ):
+ return self ._sendOperation ("add" , a , b )
+
+
+ def subtract (self , a , b ):
+ return self ._sendOperation ("subtract" , a , b )
+
+
+ def multiply (self , a , b ):
+ return self ._sendOperation ("multiply" , a , b )
+
+
+ def divide (self , a , b ):
+ return self ._sendOperation ("divide" , a , b )
+
+
+
+
More good practices
+
+
Testing scheduling
+
+
When testing code that involves the passage of time, waiting e.g. for a two hour timeout to occur in a test is not very realistic. Twisted provides a solution to this, the Clock
class that allows one to simulate the passage of time.
+
+
As an example we'll test the code for client request timeout: since our client
+uses TCP it can hang for a long time (firewall, connectivity problems, etc...).
+So generally we need to implement timeouts on the client side. Basically it's
+just that we send a request, don't receive a response and expect a timeout error
+to be triggered after a certain duration.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
from calculus .client_2 import RemoteCalculationClient , ClientTimeoutError
+
+from twisted .internet import task
+from twisted .trial import unittest
+from twisted .test import proto_helpers
+
+
+
+class ClientCalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ self .tr = proto_helpers .StringTransportWithDisconnection ()
+ self .clock = task .Clock ()
+ self .proto = RemoteCalculationClient ()
+ self .tr .protocol = self .proto
+ self .proto .callLater = self .clock .callLater
+ self .proto .makeConnection (self .tr )
+
+
+ def _test (self , operation , a , b , expected ):
+ d = getattr (self .proto , operation )(a , b )
+ self .assertEqual (self .tr .value (), '%s %d %d\r\n' % (operation , a , b ))
+ self .tr .clear ()
+ d .addCallback (self .assertEqual , expected )
+ self .proto .dataReceived ("%d\r\n" % (expected ,))
+ return d
+
+
+ def test_add (self ):
+ return self ._test ('add' , 7 , 6 , 13 )
+
+
+ def test_subtract (self ):
+ return self ._test ('subtract' , 82 , 78 , 4 )
+
+
+ def test_multiply (self ):
+ return self ._test ('multiply' , 2 , 8 , 16 )
+
+
+ def test_divide (self ):
+ return self ._test ('divide' , 14 , 3 , 4 )
+
+
+ def test_timeout (self ):
+ d = self .proto .add (9 , 4 )
+ self .assertEqual (self .tr .value (), 'add 9 4\r\n' )
+ self .clock .advance (self .proto .timeOut )
+ return self .assertFailure (d , ClientTimeoutError )
+
+
+
What happens here? We instantiate our protocol as usual, the only trick
+is to create the clock, and assign proto.callLater
to
+ clock.callLater
. Thus, every callLater calls in the protocol
+will finish before clock.advance()
returns.
+
+
In the new test (test_timeout), we call clock.advance
, that simulates and advance in time
+(logically it's similar to a time.sleep
call). And
+we just have to verify that our Deferred got a timeout error.
+
+
Let's implement that in our code.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
+
+from twisted .protocols import basic
+from twisted .internet import defer , reactor
+
+
+
+class ClientTimeoutError (Exception ):
+ pass
+
+
+
+class RemoteCalculationClient (basic .LineReceiver ):
+
+ callLater = reactor .callLater
+ timeOut = 60
+
+ def __init__ (self ):
+ self .results = []
+
+
+ def lineReceived (self , line ):
+ d , callID = self .results .pop (0 )
+ callID .cancel ()
+ d .callback (int (line ))
+
+
+ def _cancel (self , d ):
+ d .errback (ClientTimeoutError ())
+
+
+ def _sendOperation (self , op , a , b ):
+ d = defer .Deferred ()
+ callID = self .callLater (self .timeOut , self ._cancel , d )
+ self .results .append ((d , callID ))
+ line = "%s %d %d" % (op , a , b )
+ self .sendLine (line )
+ return d
+
+
+ def add (self , a , b ):
+ return self ._sendOperation ("add" , a , b )
+
+
+ def subtract (self , a , b ):
+ return self ._sendOperation ("subtract" , a , b )
+
+
+ def multiply (self , a , b ):
+ return self ._sendOperation ("multiply" , a , b )
+
+
+ def divide (self , a , b ):
+ return self ._sendOperation ("divide" , a , b )
+
+
+
The only important thing here is to not forget to cancel our callLater
+when everything went fine.
+
+
Cleaning up after tests
+
+
This chapter is mainly intended for people that want to have sockets or
+processes created in their tests. If it's still not obvious, you must try to
+avoid that like the plague, because it ends up with a lot of problems, one of
+them being intermittent failures. And intermittent failures are the plague
+of automated tests.
+
+
To actually test that, we'll launch a server with our protocol.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
from calculus .remote_1 import RemoteCalculationFactory
+from calculus .client_2 import RemoteCalculationClient
+
+from twisted .trial import unittest
+from twisted .internet import reactor , protocol
+
+
+
+class RemoteRunCalculationTestCase (unittest .TestCase ):
+
+ def setUp (self ):
+ factory = RemoteCalculationFactory ()
+ self .port = reactor .listenTCP (0 , factory , interface ="127.0.0.1" )
+ self .client = None
+
+
+ def tearDown (self ):
+ if self .client is not None :
+ self .client .transport .loseConnection ()
+ return self .port .stopListening ()
+
+
+ def _test (self , op , a , b , expected ):
+ creator = protocol .ClientCreator (reactor , RemoteCalculationClient )
+ def cb (client ):
+ self .client = client
+ return getattr (self .client , op )(a , b
+ ).addCallback (self .assertEqual , expected )
+ return creator .connectTCP ('127.0.0.1' , self .port .getHost ().port
+ ).addCallback (cb )
+
+
+ def test_add (self ):
+ return self ._test ("add" , 5 , 9 , 14 )
+
+
+ def test_subtract (self ):
+ return self ._test ("subtract" , 47 , 13 , 34 )
+
+
+ def test_multiply (self ):
+ return self ._test ("multiply" , 7 , 3 , 21 )
+
+
+ def test_divide (self ):
+ return self ._test ("divide" , 84 , 10 , 8 )
+
+
+
Recent versions of trial will fail loudly if you remove the
+ stopListening
call, which is good.
+
+
Also, you should be aware that tearDown
will
+called in any case, after success or failure. So don't expect that every
+objects you created in the test method are present, because your tests may
+have failed in the middle.
+
+
Trial also has a addCleanup
method, which makes
+these kind of cleanups easy and removes the need for tearDown
+
. For example, you could remove the code in _test
+this way:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
def setUp (self ):
+ factory = RemoteCalculationFactory ()
+ self .port = reactor .listenTCP (0 , factory , interface ="127.0.0.1" )
+ self .addCleanup (self .port .stopListening )
+
+def _test (self , op , a , b , expected ):
+ creator = protocol .ClientCreator (reactor , RemoteCalculationClient )
+ def cb (client ):
+ self .addCleanup (self .client .transport .loseConnection )
+ return getattr (client , op )(a , b ).addCallback (self .assertEqual , expected )
+ return creator .connectTCP ('127.0.0.1' , self .port .getHost ().port ).addCallback (cb )
+
+
+
This remove the need of a tearDown method, and you don't have to check for
+the value of self.client: you only call addCleanup when the client is
+created.
+
+
Handling logged errors
+
+
Currently, if you send an invalid command or invalid arguments to our
+server, it logs an exception and closes the connection. This is a perfectly
+valid behavior, but for the sake of this tutorial, we want to return an error
+to the user if he sends invalid operators, and log any errors on server side.
+So we'll want a test like this:
+
+
1
+2
+3
+
def test_invalidParameters (self ):
+ self .proto .dataReceived ('add foo bar\r\n' )
+ self .assertEqual (self .tr .value (), "error\r\n" )
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
+
+from twisted .protocols import basic
+from twisted .internet import protocol
+from twisted .python import log
+from calculus .base_3 import Calculation
+
+
+
+class CalculationProxy (object ):
+ def __init__ (self ):
+ self .calc = Calculation ()
+ for m in ['add' , 'subtract' , 'multiply' , 'divide' ]:
+ setattr (self , 'remote_%s' % m , getattr (self .calc , m ))
+
+
+
+class RemoteCalculationProtocol (basic .LineReceiver ):
+ def __init__ (self ):
+ self .proxy = CalculationProxy ()
+
+
+ def lineReceived (self , line ):
+ op , a , b = line .split ()
+ op = getattr (self .proxy , 'remote_%s' % (op ,))
+ try :
+ result = op (a , b )
+ except TypeError :
+ log .err ()
+ self .sendLine ("error" )
+ else :
+ self .sendLine (str (result ))
+
+
+
+class RemoteCalculationFactory (protocol .Factory ):
+ protocol = RemoteCalculationProtocol
+
+
+
+def main ():
+ from twisted .internet import reactor
+ from twisted .python import log
+ import sys
+ log .startLogging (sys .stdout )
+ reactor .listenTCP (0 , RemoteCalculationFactory ())
+ reactor .run ()
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
If you try something like that, it will not work. Here is the output you should have:
+
+
+trial calculus.test.test_remote_3.RemoteCalculationTestCase.test_invalidParameters
+calculus.test.test_remote_3
+ RemoteCalculationTestCase
+ test_invalidParameters ... [ERROR]
+
+===============================================================================
+[ERROR]: calculus.test.test_remote_3.RemoteCalculationTestCase.test_invalidParameters
+
+Traceback (most recent call last):
+ File "/tmp/calculus/remote_2.py", line 27, in lineReceived
+ result = op(a, b)
+ File "/tmp/calculus/base_3.py", line 11, in add
+ a, b = self._make_ints(a, b)
+ File "/tmp/calculus/base_3.py", line 8, in _make_ints
+ raise TypeError
+exceptions.TypeError:
+-------------------------------------------------------------------------------
+Ran 1 tests in 0.004s
+
+FAILED (errors=1)
+
+
+
At first, you could think there is a problem, because you catch this
+exception. But in fact trial doesn't let you do that without controlling it:
+you must expect logged errors and clean them. To do that, you have to use the
+ flushLoggedErrors
method. You call it with the
+exception you expect, and it returns the list of exceptions logged since the
+start of the test. Generally, you'll want to check that this list has the
+expected length, or possibly that each exception has an expected message. We do
+the former in our test:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
from calculus .remote_2 import RemoteCalculationFactory
+from twisted .trial import unittest
+from twisted .test import proto_helpers
+
+
+
+class RemoteCalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ factory = RemoteCalculationFactory ()
+ self .proto = factory .buildProtocol (('127.0.0.1' , 0 ))
+ self .tr = proto_helpers .StringTransport ()
+ self .proto .makeConnection (self .tr )
+
+
+ def _test (self , operation , a , b , expected ):
+ self .proto .dataReceived ('%s %d %d\r\n' % (operation , a , b ))
+ self .assertEqual (int (self .tr .value ()), expected )
+
+
+ def test_add (self ):
+ return self ._test ('add' , 7 , 6 , 13 )
+
+
+ def test_subtract (self ):
+ return self ._test ('subtract' , 82 , 78 , 4 )
+
+
+ def test_multiply (self ):
+ return self ._test ('multiply' , 2 , 8 , 16 )
+
+
+ def test_divide (self ):
+ return self ._test ('divide' , 14 , 3 , 4 )
+
+
+ def test_invalidParameters (self ):
+ self .proto .dataReceived ('add foo bar\r\n' )
+ self .assertEqual (self .tr .value (), "error\r\n" )
+ errors = self .flushLoggedErrors (TypeError )
+ self .assertEqual (len (errors ), 1 )
+
+
+
Resolve a bug
+
+
A bug was left over during the development of the timeout (probably several
+bugs, but that's not the point), concerning the reuse of the protocol when you
+got a timeout: the connection is not dropped, so you can get timeout forever.
+Generally an user will come to you saying "I have this strange problem on
+my crappy network environment. It seems you could solve it with doing XXX at
+YYY."
+
+
Actually, this bug can be corrected several ways. But if you correct it
+without adding tests, one day you'll face a big problem: regression.
+So the first step is adding a failing test.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
from calculus .client_3 import RemoteCalculationClient , ClientTimeoutError
+
+from twisted .internet import task
+from twisted .trial import unittest
+from twisted .test import proto_helpers
+
+
+
+class ClientCalculationTestCase (unittest .TestCase ):
+ def setUp (self ):
+ self .tr = proto_helpers .StringTransportWithDisconnection ()
+ self .clock = task .Clock ()
+ self .proto = RemoteCalculationClient ()
+ self .tr .protocol = self .proto
+ self .proto .callLater = self .clock .callLater
+ self .proto .makeConnection (self .tr )
+
+
+ def _test (self , operation , a , b , expected ):
+ d = getattr (self .proto , operation )(a , b )
+ self .assertEqual (self .tr .value (), '%s %d %d\r\n' % (operation , a , b ))
+ self .tr .clear ()
+ d .addCallback (self .assertEqual , expected )
+ self .proto .dataReceived ("%d\r\n" % (expected ,))
+ return d
+
+
+ def test_add (self ):
+ return self ._test ('add' , 7 , 6 , 13 )
+
+
+ def test_subtract (self ):
+ return self ._test ('subtract' , 82 , 78 , 4 )
+
+
+ def test_multiply (self ):
+ return self ._test ('multiply' , 2 , 8 , 16 )
+
+
+ def test_divide (self ):
+ return self ._test ('divide' , 14 , 3 , 4 )
+
+
+ def test_timeout (self ):
+ d = self .proto .add (9 , 4 )
+ self .assertEqual (self .tr .value (), 'add 9 4\r\n' )
+ self .clock .advance (self .proto .timeOut )
+ return self .assertFailure (d , ClientTimeoutError )
+
+
+ def test_timeoutConnectionLost (self ):
+ called = []
+ def lost (arg ):
+ called .append (True )
+ self .proto .connectionLost = lost
+
+ d = self .proto .add (9 , 4 )
+ self .assertEqual (self .tr .value (), 'add 9 4\r\n' )
+ self .clock .advance (self .proto .timeOut )
+
+ def check (ignore ):
+ self .assertEqual (called , [True ])
+ return self .assertFailure (d , ClientTimeoutError ).addCallback (check )
+
+
What have we done here ?
+
+ We switched to StringTransportWithDisconnection. This transport manages
+ loseConnection
and forwards it to its protocol.
+ We assign the protocol to the transport via the protocol
+
attribute.
+ We check that after a timeout our connection has closed.
+
+
+
+
For doing that, we then use the TimeoutMixin
+class, that does almost everything we want. The great thing is that it almost
+changes nothing to our class.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+
+
+from twisted .protocols import basic , policies
+from twisted .internet import defer
+
+
+
+class ClientTimeoutError (Exception ):
+ pass
+
+
+
+class RemoteCalculationClient (object , basic .LineReceiver , policies .TimeoutMixin ):
+
+ def __init__ (self ):
+ self .results = []
+ self ._timeOut = 60
+
+ def lineReceived (self , line ):
+ self .setTimeout (None )
+ d = self .results .pop (0 )
+ d .callback (int (line ))
+
+
+ def timeoutConnection (self ):
+ for d in self .results :
+ d .errback (ClientTimeoutError ())
+ self .transport .loseConnection ()
+
+
+ def _sendOperation (self , op , a , b ):
+ d = defer .Deferred ()
+ self .results .append (d )
+ line = "%s %d %d" % (op , a , b )
+ self .sendLine (line )
+ self .setTimeout (self ._timeOut )
+ return d
+
+
+ def add (self , a , b ):
+ return self ._sendOperation ("add" , a , b )
+
+
+ def subtract (self , a , b ):
+ return self ._sendOperation ("subtract" , a , b )
+
+
+ def multiply (self , a , b ):
+ return self ._sendOperation ("multiply" , a , b )
+
+
+ def divide (self , a , b ):
+ return self ._sendOperation ("divide" , a , b )
+
+
+
Code coverage
+
+
Code coverage is one of the aspects of software testing that shows how much
+your tests cross (cover) the code of your program. There are different kind of
+measures: path coverage, condition coverage, statement coverage... We'll only
+consider statement coverage here, whether a line has been executed or not.
+
+
+
Trial has an option to generate the statement coverage of your tests.
+This option is --coverage. It creates a coverage directory in _trial_temp,
+with a file .cover for every modules used during the tests. The ones
+interesting for us are calculus.base.cover and calculus.remote.cover. In
+front of each line is the number of times you went through during the
+tests, or the marker '>>>>>>' if the line was not
+covered. If you went through all the tutorial to this point, you should
+have complete coverage :).
+
+
Again, this is only another useful pointer, but it doesn't mean your
+code is perfect: your tests should consider every possibile input and
+output, to get full coverage (condition, path, etc.) as well
+.
+
+
Conclusion
+
+
So what did you learn in this document?
+
+ How to use the trial command-line tool to run your tests
+ How to use string transports to test individual clients and servers
+ without creating sockets
+ If you really want to create sockets, how to cleanly do it so that it
+ doesn't have bad side effects
+ And some small tips you can't live without.
+
+If one of the topics still looks cloudy to you, please give us your feedback!
+You can file tickets to improve this document
+
on the Twisted web site .
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/backends.html b/doc/core/howto/tutorial/backends.html
new file mode 100644
index 0000000..f29533e
--- /dev/null
+++ b/doc/core/howto/tutorial/backends.html
@@ -0,0 +1,1348 @@
+
+
+Twisted Documentation: The Evolution of Finger: pluggable backends
+
+
+
+
+ The Evolution of Finger: pluggable backends
+
+
+
+
+
+
Introduction
+
+
This is the fifth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part we will add new several new backends to our finger service using
+the component-based architecture developed in The
+Evolution of Finger: moving to a component based architecture . This will
+show just how convenient it is to implement new back-ends 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.
+
+
Another Back-end
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
from twisted .internet import protocol , reactor , defer , utils
+import pwd
+
+
+
+class LocalFingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def getUser (self , user ):
+
+ return utils .getProcessOutput ("finger" , [user ])
+
+ def getUsers (self ):
+ return defer .succeed ([])
+
+
+f = LocalFingerService ()
+
+
+Full source code here:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer , utils
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc
+from zope .interface import Interface , implements
+import cgi
+import pwd
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (user , status ):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ implements (resource .IResource )
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service = service
+ self .putChild ('RPC2' , UserStatusXR (self .service ))
+
+ def render_GET (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 ):
+ if path =="" :
+ return UserStatusTree (self .service )
+ else :
+ return UserStatus (path , self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService ,
+ resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def render_GET (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 .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+
+
+class LocalFingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def getUser (self , user ):
+
+ return utils .getProcessOutput ("finger" , [user ])
+
+ def getUsers (self ):
+ return defer .succeed ([])
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = LocalFingerService ()
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+
+
+
+
We've already written this, but now we get more for less work:
+the network code is completely separate from the back-end.
+
+
+
Yet Another Back-end: Doing the Standard Thing
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
from twisted .internet import protocol , reactor , defer , utils
+import pwd
+import os
+
+
+
+
+class LocalFingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def getUser (self , user ):
+ user = user .strip ()
+ try :
+ entry = pwd .getpwnam (user )
+ except KeyError :
+ return defer .succeed ("No such user" )
+ try :
+ f = file (os .path .join (entry [5 ],'.plan' ))
+ except (IOError , OSError ):
+ return defer .succeed ("No such user" )
+ data = f .read ()
+ data = data .strip ()
+ f .close ()
+ return defer .succeed (data )
+
+ def getUsers (self ):
+ return defer .succeed ([])
+
+
+
+f = LocalFingerService ()
+
+
+Full source code here:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer , utils
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc
+from zope .interface import Interface , implements
+import cgi
+import pwd
+import os
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (user , status ):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade ():
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ implements (resource .IResource )
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service = service
+ self .putChild ('RPC2' , UserStatusXR (self .service ))
+
+ def render_GET (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 ):
+ if path =="" :
+ return UserStatusTree (self .service )
+ else :
+ return UserStatus (path , self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService ,
+ resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def render_GET (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 .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+
+
+class LocalFingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def getUser (self , user ):
+ user = user .strip ()
+ try :
+ entry = pwd .getpwnam (user )
+ except KeyError :
+ return defer .succeed ("No such user" )
+ try :
+ f = file (os .path .join (entry [5 ],'.plan' ))
+ except (IOError , OSError ):
+ return defer .succeed ("No such user" )
+ data = f .read ()
+ data = data .strip ()
+ f .close ()
+ return defer .succeed (data )
+
+ def getUsers (self ):
+ return defer .succeed ([])
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = LocalFingerService ()
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+
+
+
+
Not much to say except that now we can be churn out backends like crazy. Feel
+like doing a back-end for Advogato , for
+example? Dig out the XML-RPC client support Twisted has, and get to work!
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/client.html b/doc/core/howto/tutorial/client.html
new file mode 100644
index 0000000..8d1c4b8
--- /dev/null
+++ b/doc/core/howto/tutorial/client.html
@@ -0,0 +1,260 @@
+
+
+Twisted Documentation: The Evolution of Finger: a Twisted finger client
+
+
+
+
+ The Evolution of Finger: a Twisted finger client
+
+
+
+
+
+
Introduction
+
+
This is the ninth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we develop a client for the finger server: a proxy finger
+server which forwards requests to another finger server.
+
+
Finger Proxy
+
+
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 less flexible than
+using the factory directly, it's also more convenient.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+
+from twisted .application import internet , service
+from twisted .internet import defer , protocol , reactor
+from twisted .protocols import basic
+from twisted .python import components
+from zope .interface import Interface , implements
+
+
+def catchError (err ):
+ return "Internal error in server"
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """Return a deferred returning a string"""
+
+ def getUsers ():
+ """Return a deferred returning a list of strings"""
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """Return a deferred returning a string"""
+
+ def buildProtocol (addr ):
+ """Return a protocol returning a string"""
+
+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 FingerFactoryFromService (protocol .ClientFactory ):
+
+ implements (IFingerFactory )
+
+ protocol = FingerProtocol
+
+ def __init__ (self , service ):
+ self .service = service
+
+ def getUser (self , user ):
+ return self .service .getUser (user )
+
+
+components .registerAdapter (FingerFactoryFromService ,
+ IFingerService ,
+ IFingerFactory )
+
+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 , reason ):
+ 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 (service .Service ):
+ implements (IFingerService )
+
+ def getUser (self , user ):
+ try :
+ user , host = user .split ('@' , 1 )
+ except :
+ user = user .strip ()
+ host = '127.0.0.1'
+ ret = finger (user , host )
+ ret .addErrback (lambda _ : "Could not connect to remote host" )
+ return ret
+
+ def getUsers (self ):
+ return defer .succeed ([])
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = ProxyFingerService ()
+internet .TCPServer (7779 , IFingerFactory (f )).setServiceParent (
+ service .IServiceCollection (application ))
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/components.html b/doc/core/howto/tutorial/components.html
new file mode 100644
index 0000000..a2d3586
--- /dev/null
+++ b/doc/core/howto/tutorial/components.html
@@ -0,0 +1,1132 @@
+
+
+Twisted Documentation: The Evolution of Finger: moving to a component based architecture
+
+
+
+
+ The Evolution of Finger: moving to a component based architecture
+
+
+
+
+
+
Introduction
+
+
This is the fourth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this section of the tutorial, we'll move our code to a component
+architecture so that adding new features is trivial.
+See Interfaces and Adapters for a more
+complete discussion of components.
+
+
Write Maintainable Code
+
+
+
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
+rereading the file every 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 a call to zope.interface.implements
. Then, adapters
+are used to make this service look like an appropriate class for various things:
+for supplying a finger factory to TCPServer
, for supplying a
+resource to site's constructor, and to provide an IRC client factory
+for TCPClient
. All the adapters use are the methods
+in FingerService
they are declared to use:
+getUser
/getUsers
. We could, of course, skip 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.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc
+from zope .interface import Interface , implements
+import cgi
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ implements (resource .IResource )
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service = service
+ self .putChild ('RPC2' , UserStatusXR (self .service ))
+
+ def render_GET (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 ):
+ if path =="" :
+ return UserStatusTree (self .service )
+ else :
+ return UserStatus (path , self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService ,
+ resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def render_GET (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 .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+
+
+
Advantages of Latest Version
+
+
+Readable -- each class is short
+Maintainable -- each class knows only about interfaces
+Dependencies between code parts are minimized
+Example: writing a new IFingerService
is easy
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
class IFingerSetterService (Interface ):
+
+ def setUser (user , status ):
+ """Set the user's status to something"""
+
+
+
+class MemoryFingerService (service .Service ):
+
+ implements ([IFingerService , IFingerSetterService ])
+
+ def __init__ (self , **kwargs ):
+ self .users = kwargs
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def setUser (self , user , status ):
+ self .users [user ] = status
+
+
+f = MemoryFingerService (moshez ='Happy and well' )
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (1079 , IFingerSetterFactory (f ), interface ='127.0.0.1'
+ ).setServiceParent (serviceCollection )
+
+
+Full source code here:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc
+from zope .interface import Interface , implements
+import cgi
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """Return a deferred returning a string"""
+
+ def getUsers ():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """Return a deferred returning a string"""
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """Return a deferred returning a string"""
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """Return a deferred returning a string"""
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+class UserStatusTree (resource .Resource ):
+
+ implements (resource .IResource )
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service = service
+ self .putChild ('RPC2' , UserStatusXR (self .service ))
+
+ def render_GET (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 ):
+ if path =="" :
+ return UserStatusTree (self .service )
+ else :
+ return UserStatus (path , self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService ,
+ resource .IResource )
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def render_GET (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 .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+class MemoryFingerService (service .Service ):
+
+ implements ([IFingerService , IFingerSetterService ])
+
+ def __init__ (self , **kwargs ):
+ self .users = kwargs
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def setUser (self , user , status ):
+ self .users [user ] = status
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = MemoryFingerService (moshez ='Happy and well' )
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (1079 , IFingerSetterFactory (f ), interface ='127.0.0.1'
+ ).setServiceParent (serviceCollection )
+
+
+
+
Aspect-Oriented Programming
+
+
At last, an example of aspect-oriented programming that isn't about logging
+or timing. This code is actually useful! Watch how aspect-oriented programming
+helps you write less code and have fewer dependencies!
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/configuration.html b/doc/core/howto/tutorial/configuration.html
new file mode 100644
index 0000000..e905587
--- /dev/null
+++ b/doc/core/howto/tutorial/configuration.html
@@ -0,0 +1,870 @@
+
+
+Twisted Documentation: The Evolution of Finger: configuration and packaging of the finger service
+
+
+
+
+ The Evolution of Finger: configuration and packaging of the finger service
+
+
+
+
+
+
Introduction
+
+
This is the eleventh part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we make it easier for non-programmers to configure a finger
+server and show how to package it in the .deb and RPM package formats. Plugins
+are discussed further in the Twisted Plugin System
+howto. Writing twistd plugins is covered in Writing a
+twistd Plugin , and .tac applications are covered in Using the Twisted Application Framework .
+
+
Plugins
+
+
So far, the user had to be somewhat of a programmer to be able to configure
+stuff. Maybe we can eliminate even that? Move old code
+to finger/__init__.py
and...
+
+Full source code for finger module here:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+
+
+from zope .interface import Interface , implements
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components , log
+from twisted .web import resource , server , xmlrpc
+from twisted .spread import pb
+
+from OpenSSL import SSL
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ template = """<html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %(users)s
+ </ul>
+ </body>
+ </html>"""
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service = service
+
+ def getChild (self , path , request ):
+ if path == '' :
+ return self
+ elif path == 'RPC2' :
+ return UserStatusXR (self .service )
+ else :
+ return UserStatus (path , self .service )
+
+ def render_GET (self , request ):
+ users = self .service .getUsers ()
+ def cbUsers (users ):
+ request .write (self .template % {'users' : '' .join ([
+
+ '<li><a href="%s">%s</a></li>' % (name , name )
+ for name in users ])})
+ request .finish ()
+ users .addCallback (cbUsers )
+ def ebUsers (err ):
+ log .err (err , "UserStatusTree failed" )
+ request .finish ()
+ users .addErrback (ebUsers )
+ return server .NOT_DONE_YET
+
+components .registerAdapter (UserStatusTree , IFingerService , resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ template ='''<html><head><title>%(title)s</title></head>
+ <body><h1>%(name)s</h1><p>%(status)s</p></body></html>'''
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def render_GET (self , request ):
+ status = self .service .getUser (self .user )
+ def cbStatus (status ):
+ request .write (self .template % {
+ 'title' : self .user ,
+ 'name' : self .user ,
+ 'status' : status })
+ request .finish ()
+ status .addCallback (cbStatus )
+ def ebStatus (err ):
+ log .err (err , "UserStatus failed" )
+ request .finish ()
+ status .addErrback (ebStatus )
+ return server .NOT_DONE_YET
+
+
+class UserStatusXR (xmlrpc .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+ def xmlrpc_getUsers (self ):
+ return self .service .getUsers ()
+
+
+class IPerspectiveFinger (Interface ):
+
+ def remote_getUser (username ):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers ():
+ """
+ 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 ,
+ IPerspectiveFinger )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+
+ def _read (self ):
+ self .users = {}
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+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
+
+
+
+
+
+def makeService (config ):
+
+ s = service .MultiService ()
+ f = FingerService (config ['file' ])
+ h = internet .TCPServer (1079 , IFingerFactory (f ))
+ h .setServiceParent (s )
+
+
+
+ r = resource .IResource (f )
+ r .templateDirectory = config ['templates' ]
+ site = server .Site (r )
+ j = internet .TCPServer (8000 , site )
+ j .setServiceParent (s )
+
+
+
+
+
+
+
+ if config .has_key ('ircnick' ):
+ i = IIRCClientFactory (f )
+ i .nickname = config ['ircnick' ]
+ ircserver = config ['ircserver' ]
+ b = internet .TCPClient (ircserver , 6667 , i )
+ b .setServiceParent (s )
+
+
+ if config .has_key ('pbport' ):
+ m = internet .TCPServer (
+ int (config ['pbport' ]),
+ pb .PBServerFactory (IPerspectiveFinger (f )))
+ m .setServiceParent (s )
+
+ return s
+
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
+from twisted .application import internet , service
+from twisted .internet import interfaces
+from twisted .python import usage
+import finger
+
+class Options (usage .Options ):
+
+ optParameters = [
+ ['file' , 'f' , '/etc/users' ],
+ ['templates' , 't' , '/usr/share/finger/templates' ],
+ ['ircnick' , 'n' , 'fingerbot' ],
+ ['ircserver' , None , 'irc.freenode.net' ],
+ ['pbport' , 'p' , 8889 ],
+ ]
+
+ optFlags = [['ssl' , 's' ]]
+
+def makeService (config ):
+ return finger .makeService (config )
+
+
+
And register it all:
+
+
1
+2
+3
+4
+5
+
from twisted .application .service import ServiceMaker
+
+finger = ServiceMaker (
+ 'finger' , 'finger.tap' , 'Run a finger service' , 'finger' )
+
+
+
Note that the second argument to ServiceMaker
,
+finger.tap
, is a reference to a module
+(finger/tap.py
), not to a filename.
+
+
And now, the following works
+
+
+% sudo twistd -n finger --file=/etc/users --ircnick=fingerbot
+
+
+
+ For more details about this, see the twistd plugin
+ documentation .
+
+
+
OS Integration
+
+
If we already have the finger package installed in
+ PYTHONPATH
(e.g. we added it to site-packages
), we
+can achieve easy integration:
+
+
Debian
+
+
+% tap2deb --unsigned -m "Foo <foo@example.com>" --type=python finger.tac
+% sudo dpkg -i .build/*.deb
+
+
+
Red Hat / Mandrake
+
+
+% tap2rpm --type=python finger.tac
+% sudo rpm -i *.rpm
+
+
+
These packages will properly install and register init.d
+scripts, etc. for the given file.
+
+
If it doesn't work on your favorite OS: patches accepted!
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/factory.html b/doc/core/howto/tutorial/factory.html
new file mode 100644
index 0000000..cc31631
--- /dev/null
+++ b/doc/core/howto/tutorial/factory.html
@@ -0,0 +1,713 @@
+
+
+Twisted Documentation: The Evolution of Finger: using a single factory for
+ multiple protocols
+
+
+
+
+ The Evolution of Finger: using a single factory for
+ multiple protocols
+
+
+
+
+
+
Introduction
+
+
This is the eighth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we add HTTPS support to our web frontend, showing how to have a
+single factory listen on multiple ports. More information on using SSL in
+Twisted can be found in the SSL howto .
+
+
Support HTTPS
+
+
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
+twisted.application.internet.SSLServer method. Note that one factory (in this
+case, a site) can listen on multiple ports with multiple protocols.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc , microdom
+from twisted .spread import pb
+from zope .interface import Interface , implements
+from OpenSSL import SSL
+import cgi
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service =service
+
+
+ self .putChild ("RPC2" , UserStatusXR (self .service ))
+
+
+ self .putChild ("" , self )
+
+ def _cb_render_GET (self , users , request ):
+ userOutput = '' .join (["<li><a href=\"%s\">%s</a></li>" % (user , user )
+ for user in users ])
+ request .write ("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput )
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUsers ()
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+ def getChild (self , path , request ):
+ return UserStatus (user =path , service =self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService , resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def _cb_render_GET (self , status , request ):
+ request .write ("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self .user , self .user , status ))
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUser (self .user )
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+
+class UserStatusXR (xmlrpc .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+ def xmlrpc_getUsers (self ):
+ return self .service .getUsers ()
+
+
+class IPerspectiveFinger (Interface ):
+
+ def remote_getUser (username ):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers ():
+ """
+ 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 ,
+ IPerspectiveFinger )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+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 = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+site = server .Site (resource .IResource (f ))
+internet .TCPServer (8000 , site
+ ).setServiceParent (serviceCollection )
+internet .SSLServer (443 , site , ServerContextFactory ()
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8889 , pb .PBServerFactory (IPerspectiveFinger (f ))
+ ).setServiceParent (serviceCollection )
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/index.html b/doc/core/howto/tutorial/index.html
new file mode 100644
index 0000000..e181e17
--- /dev/null
+++ b/doc/core/howto/tutorial/index.html
@@ -0,0 +1,83 @@
+
+
+Twisted Documentation: Twisted from Scratch, or The Evolution of Finger
+
+
+
+
+ Twisted from Scratch, or The Evolution of Finger
+
+
+
+
+
+
Introduction
+
+
+Twisted is a big system. People are often daunted when they approach it. It's
+hard to know where to start looking.
+
+
+
+This guide builds a full-fledged Twisted application from the ground up, using
+most of the important bits of the framework. There is a lot of code, but don't
+be afraid.
+
+
+
+The application we are looking at is a finger service, along the
+lines of the familiar service traditionally provided by UNIX⢠servers.
+We will extend this service slightly beyond the standard, in order to
+demonstrate some of Twisted's higher-level features.
+
+
+
+Each section of the tutorial dives straight into applications for various
+Twisted topics. These topics have their own introductory howtos listed in
+the core howto index and in the documentation for
+other Twisted projects like Twisted Web and Twisted Words. There are at least
+three ways to use this tutorial: you may find it useful to read through the rest
+of the topics listed in the core howto index before
+working through the finger tutorial, work through the finger tutorial and then
+go back and hit the introductory material that is relevant to the Twisted
+project you're working on, or read the introductory material one piece at a time
+as it comes up in the finger tutorial.
+
+
+
Contents
+
+
+This tutorial is split into eleven parts:
+
+
+
+The Evolution of Finger: building a simple
+finger service
+The Evolution of Finger: adding features
+to the finger service
+The Evolution of Finger: cleaning up the
+finger code
+The Evolution of Finger: moving to a
+component based architecture
+The Evolution of Finger: pluggable
+backends
+The Evolution of Finger: a web
+frontend
+The Evolution of Finger: Twisted client
+support using Perspective Broker
+The Evolution of Finger: using a single
+factory for multiple protocols
+The Evolution of Finger: a Twisted finger
+client
+The Evolution of Finger: making a finger library
+The Evolution of Finger:
+configuration and packaging of the finger service
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/intro.html b/doc/core/howto/tutorial/intro.html
new file mode 100644
index 0000000..cad8826
--- /dev/null
+++ b/doc/core/howto/tutorial/intro.html
@@ -0,0 +1,725 @@
+
+
+Twisted Documentation: The Evolution of Finger: building a simple finger service
+
+
+
+
+ The Evolution of Finger: building a simple finger service
+
+
+
+
+
+
Introduction
+
+
This is the first part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
If you're not familiar with 'finger' it's probably because it's not used as
+much nowadays as it used to be. Basically, if you run finger nail
+or finger nail@example.com
the target computer spits out some
+information about the user named nail
. For instance:
+
+
+Login: nail Name: Nail Sharp
+Directory: /home/nail Shell: /usr/bin/sh
+Last login Wed Mar 31 18:32 2004 (PST)
+New mail received Thu Apr 1 10:50 2004 (PST)
+ Unread since Thu Apr 1 10:50 2004 (PST)
+No Plan.
+
+
+
If the target computer does not have
+the fingerd
daemon
+running you'll get a "Connection Refused" error. Paranoid sysadmins
+keep fingerd
off or limit the output to hinder crackers
+and harassers. The above format is the standard fingerd
+default, but an alternate implementation can output anything it wants,
+such as automated responsibility status for everyone in an
+organization. You can also define pseudo "users", which are
+essentially keywords.
+
+
This portion of the tutorial makes use of factories and protocols as
+introduced in the Writing a TCP Server howto and
+deferreds as introduced in Using Deferreds
+and Generating Deferreds . Services and
+applications are discussed in Using the Twisted
+Application Framework .
+
+
By the end of this section of the tutorial, our finger server will answer
+TCP finger requests on port 1079, and will read data from the web.
+
+
Refuse Connections
+
+
1
+2
+
from twisted .internet import reactor
+reactor .run ()
+
+
+
This example only runs the reactor. It will consume almost no CPU
+resources. As it is not listening on any port, it can't respond to network
+requests â nothing at all will happen until we interrupt the program. At
+this point if you run finger nail
or telnet localhost
+1079
, you'll get a "Connection refused" error since there's no daemon
+running to respond. Not very useful, perhaps â but this is the skeleton
+inside which the Twisted program will grow.
+
+
+
As implied above, at various points in this tutorial you'll want to
+observe the behavior of the server being developed. Unless you have a
+finger program which can use an alternate port, the easiest way to do this
+is with a telnet client. telnet localhost 1079
will connect to
+the local host on port 1079, where a finger server will eventually be
+listening.
+
+
The Reactor
+
+
You don't call Twisted, Twisted calls you. The reactor
is Twisted's main event loop, similar to
+the main loop in other toolkits available in Python (Qt, wx, and Gtk). There is
+exactly one reactor in any running Twisted application. Once started it loops
+over and over again, responding to network events and making scheduled calls to
+code.
+
+
Note that there are actually several different reactors to choose
+from; from twisted.internet import reactor
returns the
+current reactor. If you haven't chosen a reactor class yet, it
+automatically chooses the default. See
+the Reactor Basics HOWTO for
+more information.
+
+
Do Nothing
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+
from twisted .internet import protocol , reactor
+
+class FingerProtocol (protocol .Protocol ):
+ pass
+
+class FingerFactory (protocol .ServerFactory ):
+ protocol = FingerProtocol
+
+reactor .listenTCP (1079 , FingerFactory ())
+reactor .run ()
+
+
+
Here, reactor.listenTCP
opens port 1079. (The number 1079 is a
+reminder that eventually we want to run on port 79, the standard port for
+finger servers.) The specified factory, FingerFactory
, is used to
+handle incoming requests on that port. Specifically, for each request, the
+reactor calls the factory's buildProtocol
method, which in this
+case causes FingerProtocol
to be instantiated. Since the protocol
+defined here does not actually respond to any events, connections to 1079 will
+be accepted, but the input ignored.
+
+
A Factory is the proper place for data that you want to make available to
+the protocol instances, since the protocol instances are garbage collected when
+the connection is closed.
+
+
+
Drop Connections
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
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 behavior,
+but it is already close to behaving according to the letter of the standard
+finger protocol. After all, there is no requirement to send any data to the
+remote connection in the standard. The only problem, as far as the standard is
+concerned, 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.
+
+
+
Read Username, Drop Connections
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+
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.
+
+
If you use a telnet client to interact with this server, the result will
+look something like this:
+
+
+$ telnet localhost 1079
+Trying 127.0.0.1...
+Connected to localhost.localdomain.
+alice
+Connection closed by foreign host.
+
+
+
Congratulations, this is the first standard-compliant version of the code.
+However, usually people actually expect some data about users to be
+transmitted.
+
+
Read Username, Output Error, Drop Connections
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+
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 devastating effect in honey-pots (decoy servers), of
+course.
+
+
+
Output From Empty Factory
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
+
+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 behavior, but finally we see what usefulness 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 back-end changes.
+
+
+
Output from Non-empty Factory
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
+
+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.
+
+
+
Use Deferreds
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
+
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+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 the result from a remote Oracle server? By
+allowing getUser to return a Deferred, we make it easier for the data to be
+retrieved asynchronously so that the CPU can be used for other tasks in the
+meanwhile.
+
+
As described in the Deferred HOWTO , Deferreds
+allow a program to be driven by events. For instance, if one task in a program
+is waiting on data, rather than have the CPU (and the program!) idly waiting
+for that data (a process normally called 'blocking'), the program can perform
+other operations in the meantime, and waits for some signal that data is ready
+to be processed before returning to that process.
+
+
In brief, the code in FingerFactory
above creates a
+Deferred, to which we start to attach callbacks . The
+deferred action in FingerFactory
is actually a
+fast-running expression consisting of one dictionary
+method, get
. Since this action can execute without
+delay, FingerFactory.getUser
+uses defer.succeed
to create a Deferred which already has
+a result, meaning its return value will be passed immediately to the
+first callback function, which turns out to
+be FingerProtocol.writeResponse
. We've also defined
+an errback (appropriately
+named FingerProtocol.onError
) that will be called instead
+of writeResponse
if something goes wrong.
+
+
Run 'finger' Locally
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
+
+from twisted .internet import protocol , reactor , defer , utils
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+class FingerFactory (protocol .ServerFactory ):
+ protocol = FingerProtocol
+
+ def getUser (self , user ):
+ return utils .getProcessOutput ("finger" , [user ])
+
+reactor .listenTCP (1079 , FingerFactory ())
+reactor .run ()
+
+
+
This example also makes use of a
+Deferred. twisted.internet.utils.getProcessOutput
is a
+non-blocking version of Python's commands.getoutput
: it
+runs a shell command (finger
, in this case) and captures
+its standard output. However, getProcessOutput
returns a
+Deferred instead of the output itself.
+Since FingerProtocol.lineReceived
is already expecting a
+Deferred to be returned by getUser
, it doesn't need to be
+changed, and it returns the standard output as the finger result.
+
+
Note that in this case the shell's built-in finger
command is
+simply run with whatever arguments it is given. This is probably insecure, so
+you probably don't want a real server to do this without a lot more validation
+of the user input. This will do exactly what the standard version of the finger
+server does.
+
+
Read Status from the Web
+
+
The web. That invention which has infiltrated homes around the
+world finally gets through to our invention. In this case we use the
+built-in Twisted web client
+via twisted.web.client.getPage
, a non-blocking version of
+Python's urllib2.urlopen(URL).read()
.
+Like getProcessOutput
it returns a Deferred which will be
+called back with a string, and can thus be used as a drop-in
+replacement.
+
+
Thus, we have examples of three different database back-ends, none of which
+change the protocol class. In fact, we will not have to change the protocol
+again until the end of this tutorial: we have achieved, here, one truly usable
+class.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
+
+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 ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+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 ()
+
+
+
Use Application
+
+
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
+built-in way to do it. We will create a snippet as above, but now we will define
+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.
+
+
Read on to find out how to run this code using the twistd utility.
+
+
twistd
+
+
This is how to run Twisted Applications â files which define an
+'application'. A daemon is expected to adhere to certain behavioral standards
+so that standard tools can stop/start/query them. If a Twisted application is
+run via twistd, the TWISTed Daemonizer, all this behavioral stuff will be
+handled for you. twistd 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.
+
+
+root% twistd -ny finger11.tac # just like before
+root% twistd -y finger11.tac # daemonize, keep pid in twistd.pid
+root% twistd -y finger11.tac --pidfile=finger.pid
+root% twistd -y finger11.tac --rundir=/
+root% twistd -y finger11.tac --chroot=/var
+root% twistd -y finger11.tac -l /var/log/finger.log
+root% twistd -y finger11.tac --syslog # just log to syslog
+root% twistd -y finger11.tac --syslog --prefix=twistedfinger # use given prefix
+
+
+
There are several ways to tell twistd where your application is; here we
+show how it is done using the application
global variable in a
+Python source file (a Twisted Application
+Configuration file).
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
+
+
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+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 = service .Application ('finger' , uid =1 , gid =1 )
+factory = FingerFactory (moshez ='Happy and well' )
+internet .TCPServer (79 , factory ).setServiceParent (
+ service .IServiceCollection (application ))
+
+
+
+
Instead of using reactor.listenTCP
as in the above
+examples, here we are using its application-aware
+counterpart, internet.TCPServer
. Notice that when it is
+instantiated, the application object itself does not reference either
+the protocol or the factory. Any services (such as TCPServer) which
+have the application as their parent will be started when the
+application is started by twistd. The application object is more
+useful for returning an object that supports the IService
, IServiceCollection
, IProcess
,
+and sob.IPersistable
+interfaces with the given parameters; we'll be seeing these in the
+next part of the tutorial. As the parent of the TCPServer we opened,
+the application lets us manage the TCPServer.
+
+
With the daemon running on the standard finger port, you can test it with
+the standard finger command: finger moshez
.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/library.html b/doc/core/howto/tutorial/library.html
new file mode 100644
index 0000000..fd426fe
--- /dev/null
+++ b/doc/core/howto/tutorial/library.html
@@ -0,0 +1,271 @@
+
+
+Twisted Documentation: The Evolution of Finger: making a finger library
+
+
+
+
+ The Evolution of Finger: making a finger library
+
+
+
+
+
+
Introduction
+
+
This is the tenth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we separate the application code that launches a finger service
+from the library code which defines a finger service, placing the application in
+a Twisted Application Configuration (.tac) file. We also move configuration
+(such as HTML templates) into separate files. Configuration and deployment with
+.tac and twistd are introduced in Using the
+Twisted Application Framework .
+
+
Organization
+
+
Now this code, while quite modular and well-designed, isn't
+properly organized. Everything above the application=
belongs in a
+module, and the HTML templates all belong in separate files.
+
+
+
We can use the templateFile
and templateDirectory
+attributes to indicate what HTML template file to use for each Page, and where
+to look for it.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
+
+
+import finger
+
+from twisted .internet import protocol , reactor , defer
+from twisted .spread import pb
+from twisted .web import resource , server
+from twisted .application import internet , service , strports
+from twisted .python import log
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = finger .FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 , finger .IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+
+site = server .Site (resource .IResource (f ))
+internet .TCPServer (8000 , site
+ ).setServiceParent (serviceCollection )
+
+internet .SSLServer (443 , site , finger .ServerContextFactory ()
+ ).setServiceParent (serviceCollection )
+
+i = finger .IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+
+internet .TCPServer (8889 , pb .PBServerFactory (finger .IPerspectiveFinger (f ))
+ ).setServiceParent (serviceCollection )
+
+
+
+Note that our program is now quite separated. We have:
+
+ Code (in the module)
+ Configuration (file above)
+ Presentation (templates)
+ Content (/etc/users
)
+ Deployment (twistd)
+
+
+Prototypes don't need this level of separation, so our earlier examples all
+bunched together. However, real applications do. Thankfully, if we write our
+code correctly, it is easy to achieve a good separation of parts.
+
+
+
+
Easy Configuration
+
+
We can also supply easy configuration for common cases with a makeService
+method that will also help build .tap files later:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
+
+
+def makeService (config ):
+
+ s = service .MultiService ()
+ f = FingerService (config ['file' ])
+ h = internet .TCPServer (79 , IFingerFactory (f ))
+ h .setServiceParent (s )
+
+
+ r = resource .IResource (f )
+ r .templateDirectory = config ['templates' ]
+ site = server .Site (r )
+ j = internet .TCPServer (8000 , site )
+ j .setServiceParent (s )
+
+
+ if config .get ('ssl' ):
+ k = internet .SSLServer (443 , site , ServerContextFactory ())
+ k .setServiceParent (s )
+
+
+ if config .has_key ('ircnick' ):
+ i = IIRCClientFactory (f )
+ i .nickname = config ['ircnick' ]
+ ircserver = config ['ircserver' ]
+ b = internet .TCPClient (ircserver , 6667 , i )
+ b .setServiceParent (s )
+
+
+ if config .has_key ('pbport' ):
+ m = internet .TCPServer (
+ int (config ['pbport' ]),
+ pb .PBServerFactory (IPerspectiveFinger (f )))
+ m .setServiceParent (s )
+
+ return s
+
+
+
And we can write simpler files now:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+
+
+
+from twisted .application import service
+
+import finger
+
+options = { 'file' : '/etc/users' ,
+ 'templates' : '/usr/share/finger/templates' ,
+ 'ircnick' : 'fingerbot' ,
+ 'ircserver' : 'irc.freenode.net' ,
+ 'pbport' : 8889 ,
+ 'ssl' : 'ssl=0' }
+
+ser = finger .makeService (options )
+application = service .Application ('finger' , uid =1 , gid =1 )
+ser .setServiceParent (service .IServiceCollection (application ))
+
+
+
+% twisted -ny simple-finger.tac
+
+
+
+
Note: the finger user still has ultimate power: he can use
+ makeService
, or he can use the lower-level interface if he has
+specific needs (maybe an IRC server 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:
+
+
+Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]
+Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.
+Ravioli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.
+...but sometimes, the user just wants to order Ravioli , so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/listings/finger/etc.users b/doc/core/howto/tutorial/listings/finger/etc.users
new file mode 100644
index 0000000..d8c8f8c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/etc.users
@@ -0,0 +1,2 @@
+moshez: happy and well
+shawn: alive
diff --git a/doc/core/howto/tutorial/listings/finger/finger/__init__.py b/doc/core/howto/tutorial/listings/finger/finger/__init__.py
new file mode 100755
index 0000000..bcb24fa
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/__init__.py
@@ -0,0 +1,3 @@
+"""
+Finger example application.
+"""
diff --git a/doc/core/howto/tutorial/listings/finger/finger/finger.py b/doc/core/howto/tutorial/listings/finger/finger/finger.py
new file mode 100755
index 0000000..7812af7
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/finger.py
@@ -0,0 +1,368 @@
+# finger.py module
+
+from zope.interface import Interface, implements
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components, log
+from twisted.web import resource, server, xmlrpc
+from twisted.spread import pb
+
+from OpenSSL import SSL
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ template = """Users
+ Users
+
+
+ """
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+
+ def getChild(self, path, request):
+ if path == '':
+ return self
+ elif path == 'RPC2':
+ return UserStatusXR(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+ def render_GET(self, request):
+ users = self.service.getUsers()
+ def cbUsers(users):
+ request.write(self.template % {'users': ''.join([
+ # Name should be quoted properly these uses.
+ '%s ' % (name, name)
+ for name in users])})
+ request.finish()
+ users.addCallback(cbUsers)
+ def ebUsers(err):
+ log.err(err, "UserStatusTree failed")
+ request.finish()
+ users.addErrback(ebUsers)
+ return server.NOT_DONE_YET
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ template='''%(title)s
+ %(name)s %(status)s
'''
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ status = self.service.getUser(self.user)
+ def cbStatus(status):
+ request.write(self.template % {
+ 'title': self.user,
+ 'name': self.user,
+ 'status': status})
+ request.finish()
+ status.addCallback(cbStatus)
+ def ebStatus(err):
+ log.err(err, "UserStatus failed")
+ request.finish()
+ status.addErrback(ebStatus)
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ 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,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+
+ def _read(self):
+ self.users = {}
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+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
+
+
+
+# Easy configuration
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(1079, IFingerFactory(f))
+ h.setServiceParent(s)
+
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+# if config.get('ssl'):
+# k = internet.SSLServer(443, site, ServerContextFactory())
+# k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/doc/core/howto/tutorial/listings/finger/finger/tap.py b/doc/core/howto/tutorial/listings/finger/finger/tap.py
new file mode 100644
index 0000000..a06102c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger/tap.py
@@ -0,0 +1,20 @@
+# finger/tap.py
+from twisted.application import internet, service
+from twisted.internet import interfaces
+from twisted.python import usage
+import finger
+
+class Options(usage.Options):
+
+ optParameters = [
+ ['file', 'f', '/etc/users'],
+ ['templates', 't', '/usr/share/finger/templates'],
+ ['ircnick', 'n', 'fingerbot'],
+ ['ircserver', None, 'irc.freenode.net'],
+ ['pbport', 'p', 8889],
+ ]
+
+ optFlags = [['ssl', 's']]
+
+def makeService(config):
+ return finger.makeService(config)
diff --git a/doc/core/howto/tutorial/listings/finger/finger01.py b/doc/core/howto/tutorial/listings/finger/finger01.py
new file mode 100755
index 0000000..0561510
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger01.py
@@ -0,0 +1,2 @@
+from twisted.internet import reactor
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger02.py b/doc/core/howto/tutorial/listings/finger/finger02.py
new file mode 100755
index 0000000..e7efbf4
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger02.py
@@ -0,0 +1,10 @@
+from twisted.internet import protocol, reactor
+
+class FingerProtocol(protocol.Protocol):
+ pass
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger03.py b/doc/core/howto/tutorial/listings/finger/finger03.py
new file mode 100755
index 0000000..d323023
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger03.py
@@ -0,0 +1,11 @@
+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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger04.py b/doc/core/howto/tutorial/listings/finger/finger04.py
new file mode 100755
index 0000000..d35f590
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger04.py
@@ -0,0 +1,12 @@
+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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger05.py b/doc/core/howto/tutorial/listings/finger/finger05.py
new file mode 100755
index 0000000..0d8da8c
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger05.py
@@ -0,0 +1,13 @@
+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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger06.py b/doc/core/howto/tutorial/listings/finger/finger06.py
new file mode 100755
index 0000000..7f78986
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger06.py
@@ -0,0 +1,18 @@
+# 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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger07.py b/doc/core/howto/tutorial/listings/finger/finger07.py
new file mode 100755
index 0000000..cc5dbf1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger07.py
@@ -0,0 +1,21 @@
+# 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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger08.py b/doc/core/howto/tutorial/listings/finger/finger08.py
new file mode 100755
index 0000000..624c5b0
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger08.py
@@ -0,0 +1,30 @@
+# 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):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger09.py b/doc/core/howto/tutorial/listings/finger/finger09.py
new file mode 100755
index 0000000..336acb3
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger09.py
@@ -0,0 +1,26 @@
+# 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):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerFactory(protocol.ServerFactory):
+ protocol = FingerProtocol
+
+ def getUser(self, user):
+ return utils.getProcessOutput("finger", [user])
+
+reactor.listenTCP(1079, FingerFactory())
+reactor.run()
diff --git a/doc/core/howto/tutorial/listings/finger/finger10.py b/doc/core/howto/tutorial/listings/finger/finger10.py
new file mode 100755
index 0000000..7e4cb93
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger10.py
@@ -0,0 +1,30 @@
+# 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):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+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()
diff --git a/doc/core/howto/tutorial/listings/finger/finger11.tac b/doc/core/howto/tutorial/listings/finger/finger11.tac
new file mode 100755
index 0000000..aae8ca6
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger11.tac
@@ -0,0 +1,34 @@
+# Read username, output from non-empty factory, drop connections
+# Use deferreds, to minimize synchronicity assumptions
+# Write application. Save in 'finger.tpy'
+
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+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 = service.Application('finger', uid=1, gid=1)
+factory = FingerFactory(moshez='Happy and well')
+internet.TCPServer(79, factory).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/finger12.tac b/doc/core/howto/tutorial/listings/finger/finger12.tac
new file mode 100755
index 0000000..69120f1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger12.tac
@@ -0,0 +1,55 @@
+# But let's try and fix setting away messages, shall we?
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+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, reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+class FingerSetterFactory(protocol.ServerFactory):
+ protocol = FingerSetterProtocol
+
+ def __init__(self, fingerFactory):
+ self.fingerFactory = fingerFactory
+
+ def setUser(self, user, status):
+ self.fingerFactory.users[user] = status
+
+ff = FingerFactory(moshez='Happy and well')
+fsf = FingerSetterFactory(ff)
+
+application = service.Application('finger', uid=1, gid=1)
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,ff).setServiceParent(serviceCollection)
+internet.TCPServer(1079,fsf).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger13.tac b/doc/core/howto/tutorial/listings/finger/finger13.tac
new file mode 100755
index 0000000..5cf60c9
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger13.tac
@@ -0,0 +1,59 @@
+# Fix asymmetry
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+class FingerSetterProtocol(basic.LineReceiver):
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self,reason):
+ user = self.lines[0]
+ status = self.lines[1]
+ self.factory.setUser(user, status)
+
+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 setUser(self, user, status):
+ self.users[user] = status
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getFingerSetterFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerSetterProtocol
+ f.setUser = self.setUser
+ return f
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79,f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079,f.getFingerSetterFactory()
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger14.tac b/doc/core/howto/tutorial/listings/finger/finger14.tac
new file mode 100755
index 0000000..48e4ee0
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger14.tac
@@ -0,0 +1,56 @@
+# Read from file
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.users = {}
+ self.filename = filename
+
+ def _read(self):
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+finger = internet.TCPServer(79, f.getFingerFactory())
+
+finger.setServiceParent(service.IServiceCollection(application))
+f.setServiceParent(service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/finger15.tac b/doc/core/howto/tutorial/listings/finger/finger15.tac
new file mode 100755
index 0000000..cf90ddc
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger15.tac
@@ -0,0 +1,87 @@
+# Read from file, announce on the web!
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class FingerResource(resource.Resource):
+
+ def __init__(self, users):
+ self.users = users
+ resource.Resource.__init__(self)
+
+ # we treat the path as the username
+ def getChild(self, username, request):
+ """
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ """
+ messagevalue = self.users.get(username)
+ username = cgi.escape(username)
+ if messagevalue is not None:
+ messagevalue = cgi.escape(messagevalue)
+ text = '%s %s
' % (username,messagevalue)
+ else:
+ text = '%s No such user
' % username
+ return static.Data(text, 'text/html')
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = FingerResource(self.users)
+ return r
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger16.tac b/doc/core/howto/tutorial/listings/finger/finger16.tac
new file mode 100755
index 0000000..54a12c7
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger16.tac
@@ -0,0 +1,101 @@
+# Read from file, announce on the web, irc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static
+
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('%s %s
' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path,
+ "No such user
usage: site/user")])),
+ 'text/html'))
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger17.tac b/doc/core/howto/tutorial/listings/finger/finger17.tac
new file mode 100755
index 0000000..ec99041
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger17.tac
@@ -0,0 +1,102 @@
+# Read from file, announce on the web, irc, xml-rpc
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.web import resource, server, static, xmlrpc
+import cgi
+
+class FingerProtocol(basic.LineReceiver):
+ def lineReceived(self, user):
+ d = self.factory.getUser(user)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ self.transport.write(message + '\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeResponse)
+
+
+class IRCReplyBot(irc.IRCClient):
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+
+ def onError(err):
+ return 'Internal error in server'
+ d.addErrback(onError)
+
+ def writeResponse(message):
+ irc.IRCClient.msg(self, user, msg+': '+message)
+ d.addCallback(writeResponse)
+
+
+class FingerService(service.Service):
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getFingerFactory(self):
+ f = protocol.ServerFactory()
+ f.protocol = FingerProtocol
+ f.getUser = self.getUser
+ return f
+
+ def getResource(self):
+ r = resource.Resource()
+ r.getChild = (lambda path, request:
+ static.Data('%s %s
' %
+ tuple(map(cgi.escape,
+ [path,self.users.get(path, "No such user")])),
+ 'text/html'))
+ x = xmlrpc.XMLRPC()
+ x.xmlrpc_getUser = self.getUser
+ r.putChild('RPC2', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger18.tac b/doc/core/howto/tutorial/listings/finger/finger18.tac
new file mode 100755
index 0000000..c39479a
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger18.tac
@@ -0,0 +1,147 @@
+# Do everything properly
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, 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_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['%s ' % (user, user)
+ for user in users]
+ return ''
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ 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_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '%s '%self.user+'%s
'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "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', x)
+ return r
+
+ def getIRCBot(self, nickname):
+ f = protocol.ReconnectingClientFactory()
+ f.protocol = IRCReplyBot
+ f.nickname = nickname
+ f.getUser = self.getUser
+ return f
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, f.getFingerFactory()
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(f.getResource())
+ ).setServiceParent(serviceCollection)
+internet.TCPClient('irc.freenode.org', 6667, f.getIRCBot('fingerbot')
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19.tac b/doc/core/howto/tutorial/listings/finger/finger19.tac
new file mode 100755
index 0000000..4b79d63
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19.tac
@@ -0,0 +1,270 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['%s ' % (user, user)
+ for user in users]
+ return ''
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '%s '%self.user+'%s
'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19a.tac b/doc/core/howto/tutorial/listings/finger/finger19a.tac
new file mode 100755
index 0000000..e6c66b5
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19a.tac
@@ -0,0 +1,231 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['%s ' % (user, user)
+ for user in users]
+ return ''
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '%s '%self.user+'%s
'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19a_changes.py b/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
new file mode 100644
index 0000000..cbb3623
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
@@ -0,0 +1,29 @@
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """Set the user's status to something"""
+
+# Advantages of latest version
+
+class MemoryFingerService(service.Service):
+
+ implements([IFingerService, IFingerSetterService])
+
+ def __init__(self, **kwargs):
+ self.users = kwargs
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def setUser(self, user, status):
+ self.users[user] = status
+
+
+f = MemoryFingerService(moshez='Happy and well')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19b.tac b/doc/core/howto/tutorial/listings/finger/finger19b.tac
new file mode 100755
index 0000000..fdf1675
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19b.tac
@@ -0,0 +1,292 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['%s ' % (user, user)
+ for user in users]
+ return ''
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '%s '%self.user+'%s
'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19b_changes.py b/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
new file mode 100644
index 0000000..3c8ff75
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
@@ -0,0 +1,19 @@
+
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+
+# Another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ # need a local finger daemon running for this to work
+ return utils.getProcessOutput("finger", [user])
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+f = LocalFingerService()
diff --git a/doc/core/howto/tutorial/listings/finger/finger19c.tac b/doc/core/howto/tutorial/listings/finger/finger19c.tac
new file mode 100755
index 0000000..98502a5
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19c.tac
@@ -0,0 +1,305 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer, utils
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc
+from zope.interface import Interface, implements
+import cgi
+import pwd
+import os
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(user, status):
+ """
+ Set the user's status to something.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade():
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ implements(resource.IResource)
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service = service
+ self.putChild('RPC2', UserStatusXR(self.service))
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ def formatUsers(users):
+ l = ['%s ' % (user, user)
+ for user in users]
+ return ''
+ d.addCallback(formatUsers)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ if path=="":
+ return UserStatusTree(self.service)
+ else:
+ return UserStatus(path, self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService,
+ resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(cgi.escape)
+ d.addCallback(lambda m:
+ '%s '%self.user+'%s
'%m)
+ d.addCallback(request.write)
+ d.addCallback(lambda _: request.finish())
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = LocalFingerService()
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger19c_changes.py b/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
new file mode 100644
index 0000000..cc592ea
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
@@ -0,0 +1,32 @@
+from twisted.internet import protocol, reactor, defer, utils
+import pwd
+import os
+
+
+# Yet another back-end
+
+class LocalFingerService(service.Service):
+
+ implements(IFingerService)
+
+ def getUser(self, user):
+ user = user.strip()
+ try:
+ entry = pwd.getpwnam(user)
+ except KeyError:
+ return defer.succeed("No such user")
+ try:
+ f = file(os.path.join(entry[5],'.plan'))
+ except (IOError, OSError):
+ return defer.succeed("No such user")
+ data = f.read()
+ data = data.strip()
+ f.close()
+ return defer.succeed(data)
+
+ def getUsers(self):
+ return defer.succeed([])
+
+
+
+f = LocalFingerService()
diff --git a/doc/core/howto/tutorial/listings/finger/finger20.tac b/doc/core/howto/tutorial/listings/finger/finger20.tac
new file mode 100755
index 0000000..d29c66f
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger20.tac
@@ -0,0 +1,285 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["%s " % (user, user)
+ for user in users])
+ request.write("""
+ Users
+ Users
+ """ % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""%s
+ %s
+ %s
+ """ % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger21.tac b/doc/core/howto/tutorial/listings/finger/finger21.tac
new file mode 100755
index 0000000..af12354
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger21.tac
@@ -0,0 +1,319 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["%s " % (user, user)
+ for user in users])
+ request.write("""
+ Users
+ Users
+ """ % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""%s
+ %s
+ %s
+ """ % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ 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,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+application = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8000, server.Site(resource.IResource(f))
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/finger22.py b/doc/core/howto/tutorial/listings/finger/finger22.py
new file mode 100755
index 0000000..4e10fc9
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger22.py
@@ -0,0 +1,337 @@
+# Do everything properly, and componentize
+from twisted.application import internet, service
+from twisted.internet import protocol, reactor, defer
+from twisted.words.protocols import irc
+from twisted.protocols import basic
+from twisted.python import components
+from twisted.web import resource, server, static, xmlrpc, microdom
+from twisted.spread import pb
+from zope.interface import Interface, implements
+from OpenSSL import SSL
+import cgi
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService(Interface):
+
+ def setUser(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+'\r\n')
+ self.transport.loseConnection()
+ d.addCallback(writeValue)
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerFactory)
+
+
+class FingerSetterProtocol(basic.LineReceiver):
+
+ def connectionMade(self):
+ self.lines = []
+
+ def lineReceived(self, line):
+ self.lines.append(line)
+
+ def connectionLost(self, reason):
+ if len(self.lines) == 2:
+ self.factory.setUser(*self.lines)
+
+
+class IFingerSetterFactory(Interface):
+
+ def setUser(user, status):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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,
+ IFingerSetterService,
+ IFingerSetterFactory)
+
+
+class IRCReplyBot(irc.IRCClient):
+
+ def connectionMade(self):
+ self.nickname = self.factory.nickname
+ irc.IRCClient.connectionMade(self)
+
+ def privmsg(self, user, channel, msg):
+ user = user.split('!')[0]
+ if self.nickname.lower() == channel.lower():
+ d = self.factory.getUser(msg)
+ d.addErrback(catchError)
+ d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
+ d.addCallback(lambda m: self.msg(user, m))
+
+
+class IIRCClientFactory(Interface):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser(user):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol(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(user)
+
+components.registerAdapter(IRCClientFactoryFromService,
+ IFingerService,
+ IIRCClientFactory)
+
+
+class UserStatusTree(resource.Resource):
+
+ def __init__(self, service):
+ resource.Resource.__init__(self)
+ self.service=service
+
+ # add a specific child for the path "RPC2"
+ self.putChild("RPC2", UserStatusXR(self.service))
+
+ # need to do this for resources at the root of the site
+ self.putChild("", self)
+
+ def _cb_render_GET(self, users, request):
+ userOutput = ''.join(["%s " % (user, user)
+ for user in users])
+ request.write("""
+ Users
+ Users
+ """ % userOutput)
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUsers()
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+ def getChild(self, path, request):
+ return UserStatus(user=path, service=self.service)
+
+components.registerAdapter(UserStatusTree, IFingerService, resource.IResource)
+
+
+class UserStatus(resource.Resource):
+
+ def __init__(self, user, service):
+ resource.Resource.__init__(self)
+ self.user = user
+ self.service = service
+
+ def _cb_render_GET(self, status, request):
+ request.write("""%s
+ %s
+ %s
+ """ % (self.user, self.user, status))
+ request.finish()
+
+ def render_GET(self, request):
+ d = self.service.getUser(self.user)
+ d.addCallback(self._cb_render_GET, request)
+
+ # signal that the rendering is not complete
+ return server.NOT_DONE_YET
+
+
+class UserStatusXR(xmlrpc.XMLRPC):
+
+ def __init__(self, service):
+ xmlrpc.XMLRPC.__init__(self)
+ self.service = service
+
+ def xmlrpc_getUser(self, user):
+ return self.service.getUser(user)
+
+ def xmlrpc_getUsers(self):
+ return self.service.getUsers()
+
+
+class IPerspectiveFinger(Interface):
+
+ def remote_getUser(username):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers():
+ """
+ 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,
+ IPerspectiveFinger)
+
+
+class FingerService(service.Service):
+
+ implements(IFingerService)
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.users = {}
+
+ def _read(self):
+ self.users.clear()
+ for line in file(self.filename):
+ user, status = line.split(':', 1)
+ user = user.strip()
+ status = status.strip()
+ self.users[user] = status
+ self.call = reactor.callLater(30, self._read)
+
+ def getUser(self, user):
+ return defer.succeed(self.users.get(user, "No such user"))
+
+ def getUsers(self):
+ return defer.succeed(self.users.keys())
+
+ def startService(self):
+ self._read()
+ service.Service.startService(self)
+
+ def stopService(self):
+ service.Service.stopService(self)
+ self.call.cancel()
+
+
+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 = service.Application('finger', uid=1, gid=1)
+f = FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+f.setServiceParent(serviceCollection)
+internet.TCPServer(79, IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+internet.SSLServer(443, site, ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+i = IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+internet.TCPServer(8889, pb.PBServerFactory(IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/fingerPBclient.py b/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
new file mode 100755
index 0000000..66ed0ae
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
@@ -0,0 +1,26 @@
+# test the PB finger on port 8889
+# this code is essentially the same as
+# the first example in howto/pb-usage
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+def gotObject(object):
+ print "got object:", object
+ object.callRemote("getUser","moshez").addCallback(gotData)
+# or
+# object.callRemote("getUsers").addCallback(gotData)
+
+def gotData(data):
+ print 'server sent:', data
+ reactor.stop()
+
+def gotNoObject(reason):
+ print "no object:",reason
+ reactor.stop()
+
+factory = pb.PBClientFactory()
+reactor.connectTCP("127.0.0.1",8889, factory)
+factory.getRootObject().addCallbacks(gotObject,gotNoObject)
+reactor.run()
+
diff --git a/doc/core/howto/tutorial/listings/finger/fingerXRclient.py b/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
new file mode 100755
index 0000000..b854bcf
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
@@ -0,0 +1,5 @@
+# testing xmlrpc finger
+
+import xmlrpclib
+server = xmlrpclib.Server('http://127.0.0.1:8000/RPC2')
+print server.getUser('moshez')
diff --git a/doc/core/howto/tutorial/listings/finger/finger_config.py b/doc/core/howto/tutorial/listings/finger/finger_config.py
new file mode 100644
index 0000000..226a26a
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/finger_config.py
@@ -0,0 +1,38 @@
+# Easy configuration
+# makeService from finger module
+
+def makeService(config):
+ # finger on port 79
+ s = service.MultiService()
+ f = FingerService(config['file'])
+ h = internet.TCPServer(79, IFingerFactory(f))
+ h.setServiceParent(s)
+
+ # website on port 8000
+ r = resource.IResource(f)
+ r.templateDirectory = config['templates']
+ site = server.Site(r)
+ j = internet.TCPServer(8000, site)
+ j.setServiceParent(s)
+
+ # ssl on port 443
+ if config.get('ssl'):
+ k = internet.SSLServer(443, site, ServerContextFactory())
+ k.setServiceParent(s)
+
+ # irc fingerbot
+ if config.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = config['ircnick']
+ ircserver = config['ircserver']
+ b = internet.TCPClient(ircserver, 6667, i)
+ b.setServiceParent(s)
+
+ # Pespective Broker on port 8889
+ if config.has_key('pbport'):
+ m = internet.TCPServer(
+ int(config['pbport']),
+ pb.PBServerFactory(IPerspectiveFinger(f)))
+ m.setServiceParent(s)
+
+ return s
diff --git a/doc/core/howto/tutorial/listings/finger/fingerproxy.tac b/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
new file mode 100644
index 0000000..839c63d
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
@@ -0,0 +1,110 @@
+# finger proxy
+from twisted.application import internet, service
+from twisted.internet import defer, protocol, reactor
+from twisted.protocols import basic
+from twisted.python import components
+from zope.interface import Interface, implements
+
+
+def catchError(err):
+ return "Internal error in server"
+
+class IFingerService(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def getUsers():
+ """Return a deferred returning a list of strings"""
+
+
+class IFingerFactory(Interface):
+
+ def getUser(user):
+ """Return a deferred returning a string"""
+
+ def buildProtocol(addr):
+ """Return a protocol returning a string"""
+
+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 FingerFactoryFromService(protocol.ClientFactory):
+
+ implements(IFingerFactory)
+
+ protocol = FingerProtocol
+
+ def __init__(self, service):
+ self.service = service
+
+ def getUser(self, user):
+ return self.service.getUser(user)
+
+
+components.registerAdapter(FingerFactoryFromService,
+ IFingerService,
+ IFingerFactory)
+
+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, reason):
+ 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(service.Service):
+ implements(IFingerService)
+
+ def getUser(self, user):
+ try:
+ user, host = user.split('@', 1)
+ except:
+ user = user.strip()
+ host = '127.0.0.1'
+ ret = finger(user, host)
+ ret.addErrback(lambda _: "Could not connect to remote host")
+ return ret
+
+ def getUsers(self):
+ return defer.succeed([])
+
+application = service.Application('finger', uid=1, gid=1)
+f = ProxyFingerService()
+internet.TCPServer(7779, IFingerFactory(f)).setServiceParent(
+ service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/organized-finger.tac b/doc/core/howto/tutorial/listings/finger/organized-finger.tac
new file mode 100644
index 0000000..2f9a129
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/organized-finger.tac
@@ -0,0 +1,31 @@
+# organized-finger.tac
+# eg: twistd -ny organized-finger.tac
+
+import finger
+
+from twisted.internet import protocol, reactor, defer
+from twisted.spread import pb
+from twisted.web import resource, server
+from twisted.application import internet, service, strports
+from twisted.python import log
+
+application = service.Application('finger', uid=1, gid=1)
+f = finger.FingerService('/etc/users')
+serviceCollection = service.IServiceCollection(application)
+internet.TCPServer(79, finger.IFingerFactory(f)
+ ).setServiceParent(serviceCollection)
+
+site = server.Site(resource.IResource(f))
+internet.TCPServer(8000, site
+ ).setServiceParent(serviceCollection)
+
+internet.SSLServer(443, site, finger.ServerContextFactory()
+ ).setServiceParent(serviceCollection)
+
+i = finger.IIRCClientFactory(f)
+i.nickname = 'fingerbot'
+internet.TCPClient('irc.freenode.org', 6667, i
+ ).setServiceParent(serviceCollection)
+
+internet.TCPServer(8889, pb.PBServerFactory(finger.IPerspectiveFinger(f))
+ ).setServiceParent(serviceCollection)
diff --git a/doc/core/howto/tutorial/listings/finger/simple-finger.tac b/doc/core/howto/tutorial/listings/finger/simple-finger.tac
new file mode 100644
index 0000000..2e75cb1
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/simple-finger.tac
@@ -0,0 +1,17 @@
+# simple-finger.tac
+# eg: twistd -ny simple-finger.tac
+
+from twisted.application import service
+
+import finger
+
+options = { 'file': '/etc/users',
+ 'templates': '/usr/share/finger/templates',
+ 'ircnick': 'fingerbot',
+ 'ircserver': 'irc.freenode.net',
+ 'pbport': 8889,
+ 'ssl': 'ssl=0' }
+
+ser = finger.makeService(options)
+application = service.Application('finger', uid=1, gid=1)
+ser.setServiceParent(service.IServiceCollection(application))
diff --git a/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py b/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
new file mode 100644
index 0000000..73361ae
--- /dev/null
+++ b/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
@@ -0,0 +1,5 @@
+
+from twisted.application.service import ServiceMaker
+
+finger = ServiceMaker(
+ 'finger', 'finger.tap', 'Run a finger service', 'finger')
diff --git a/doc/core/howto/tutorial/pb.html b/doc/core/howto/tutorial/pb.html
new file mode 100644
index 0000000..5cdfb04
--- /dev/null
+++ b/doc/core/howto/tutorial/pb.html
@@ -0,0 +1,728 @@
+
+
+Twisted Documentation: The Evolution of Finger: Twisted client support using Perspective Broker
+
+
+
+
+ The Evolution of Finger: Twisted client support using Perspective Broker
+
+
+
+
+
+
Introduction
+
+
This is the seventh part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we add a Perspective Broker service to the finger application
+so that Twisted clients can access the finger server. Perspective Broker is
+introduced in depth in its own section of the
+core howto index.
+
+
Use Perspective Broker
+
+
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.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc , microdom
+from twisted .spread import pb
+from zope .interface import Interface , implements
+import cgi
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service =service
+
+
+ self .putChild ("RPC2" , UserStatusXR (self .service ))
+
+
+ self .putChild ("" , self )
+
+ def _cb_render_GET (self , users , request ):
+ userOutput = '' .join (["<li><a href=\"%s\">%s</a></li>" % (user , user )
+ for user in users ])
+ request .write ("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput )
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUsers ()
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+ def getChild (self , path , request ):
+ return UserStatus (user =path , service =self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService , resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def _cb_render_GET (self , status , request ):
+ request .write ("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self .user , self .user , status ))
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUser (self .user )
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+
+class UserStatusXR (xmlrpc .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+ def xmlrpc_getUsers (self ):
+ return self .service .getUsers ()
+
+
+class IPerspectiveFinger (Interface ):
+
+ def remote_getUser (username ):
+ """
+ Return a user's status.
+ """
+
+ def remote_getUsers ():
+ """
+ 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 ,
+ IPerspectiveFinger )
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8889 , pb .PBServerFactory (IPerspectiveFinger (f ))
+ ).setServiceParent (serviceCollection )
+
+
+
A simple client to test the perspective broker finger:
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
+
+
+
+from twisted .spread import pb
+from twisted .internet import reactor
+
+def gotObject (object ):
+ print "got object:" , object
+ object .callRemote ("getUser" ,"moshez" ).addCallback (gotData )
+
+
+
+def gotData (data ):
+ print 'server sent:' , data
+ reactor .stop ()
+
+def gotNoObject (reason ):
+ print "no object:" ,reason
+ reactor .stop ()
+
+factory = pb .PBClientFactory ()
+reactor .connectTCP ("127.0.0.1" ,8889 , factory )
+factory .getRootObject ().addCallbacks (gotObject ,gotNoObject )
+reactor .run ()
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/protocol.html b/doc/core/howto/tutorial/protocol.html
new file mode 100644
index 0000000..d3915bb
--- /dev/null
+++ b/doc/core/howto/tutorial/protocol.html
@@ -0,0 +1,1121 @@
+
+
+Twisted Documentation: The Evolution of Finger: adding features to the finger service
+
+
+
+
+ The Evolution of Finger: adding features to the finger service
+
+
+
+
+
+
Introduction
+
+
This is the second part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this section of the tutorial, our finger server will continue to sprout
+features: the ability for users to set finger announces, and using our finger
+service to send those announcements on the web, on IRC and over XML-RPC.
+Resources and XML-RPC are introduced in the Web Applications portion of
+the Twisted Web howto . More examples
+using twisted.words.protocols.irc
can be found
+in Writing a TCP Client and
+the Twisted Words examples .
+
+
Setting Message By Local Users
+
+
Now that port 1079 is free, maybe we can use it with 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 behavior in
+our case. Testing it can be done by simply:
+
+
+
+% nc localhost 1079 # or telnet localhost 1079
+moshez
+Giving a tutorial now, sorry!
+^D
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+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 , reason ):
+ user = self .lines [0 ]
+ status = self .lines [1 ]
+ self .factory .setUser (user , status )
+
+class FingerSetterFactory (protocol .ServerFactory ):
+ protocol = FingerSetterProtocol
+
+ def __init__ (self , fingerFactory ):
+ self .fingerFactory = fingerFactory
+
+ def setUser (self , user , status ):
+ self .fingerFactory .users [user ] = status
+
+ff = FingerFactory (moshez ='Happy and well' )
+fsf = FingerSetterFactory (ff )
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 ,ff ).setServiceParent (serviceCollection )
+internet .TCPServer (1079 ,fsf ).setServiceParent (serviceCollection )
+
+
+
This program has two protocol-factory-TCPServer pairs, which are
+both child services of the application. Specifically,
+the setServiceParent
+method is used to define the two TCPServer services as children
+of application
, which implements IServiceCollection
. Both
+services are thus started with the application.
+
+
+
Use Services to Make Dependencies Sane
+
+
The previous version had the setter poke at the innards of the
+finger factory. This strategy is 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 define a common service
+class with methods that will create factories on the fly. The service
+also contains methods the factories will depend on.
+
+
The factory-creation methods, getFingerFactory
+and getFingerSetterFactory
, follow this pattern:
+
+
+
+Instantiate a generic server
+factory, twisted.internet.protocol.ServerFactory
.
+
+Set the protocol class, just like our factory class would have.
+
+Copy a service method to the factory as a function attribute. The
+function won't have access to the factory's self
, but
+that's OK because as a bound method it has access to the
+service's self
, which is what it needs.
+For getUser
, a custom method defined in the service gets
+copied. For setUser
, a standard method of
+the users
dictionary is copied.
+
+
+
+
+
Thus, 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 tutorial.
+
+
As an application service, this new finger service implements the IService
interface and
+can be started and stopped in a standardized manner. We'll make use of this in
+the next example.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+class FingerSetterProtocol (basic .LineReceiver ):
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self ,reason ):
+ user = self .lines [0 ]
+ status = self .lines [1 ]
+ self .factory .setUser (user , status )
+
+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 setUser (self , user , status ):
+ self .users [user ] = status
+
+ def getFingerFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerProtocol
+ f .getUser = self .getUser
+ return f
+
+ def getFingerSetterFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerSetterProtocol
+ f .setUser = self .setUser
+ return f
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService (moshez ='Happy and well' )
+serviceCollection = service .IServiceCollection (application )
+internet .TCPServer (79 ,f .getFingerFactory ()
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (1079 ,f .getFingerSetterFactory ()
+ ).setServiceParent (serviceCollection )
+
+
+
Most application services will want to use the Service
base class, which implements
+all the generic IService
behavior.
+
+
Read Status File
+
+
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.
+
+
+moshez: happy and well
+shawn: alive
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+
+class FingerService (service .Service ):
+ def __init__ (self , filename ):
+ self .users = {}
+ self .filename = filename
+
+ def _read (self ):
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getFingerFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerProtocol
+ f .getUser = self .getUser
+ return f
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+finger = internet .TCPServer (79 , f .getFingerFactory ())
+
+finger .setServiceParent (service .IServiceCollection (application ))
+f .setServiceParent (service .IServiceCollection (application ))
+
+
+
Since this version is reading data from a file (and refreshing the data
+every 30 seconds), there is no FingerSetterFactory
and thus
+nothing listening on port 1079.
+
+
Here we override the standard startService
+and stopService
hooks in
+the Finger service, which is set up as a child service of the
+application in the last line of the code. startService
+calls _read
, the function responsible for reading the
+data; reactor.callLater
is then used to schedule it to
+run again after thirty seconds every time it is
+called. reactor.callLater
returns an object that lets us
+cancel the scheduled run in stopService
using
+its cancel
method.
+
+
Announce on Web, Too
+
+
The same kind of service can also produce things useful for other
+protocols. For example, in twisted.web, the factory itself
+(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
.
+
+
To integrate this into the Finger application (just because we can), we set
+up a new TCPServer that calls the Site
factory and retrieves resources via a
+new function of FingerService
named getResource
.
+This function specifically returns a Resource
object with an overridden getChild
method.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .protocols import basic
+from twisted .web import resource , server , static
+import cgi
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+
+class FingerResource (resource .Resource ):
+
+ def __init__ (self , users ):
+ self .users = users
+ resource .Resource .__init__ (self )
+
+
+ def getChild (self , username , request ):
+ """
+ 'username' is a string.
+ 'request' is a 'twisted.web.server.Request'.
+ """
+ messagevalue = self .users .get (username )
+ username = cgi .escape (username )
+ if messagevalue is not None :
+ messagevalue = cgi .escape (messagevalue )
+ text = '<h1>%s</h1><p>%s</p>' % (username ,messagevalue )
+ else :
+ text = '<h1>%s</h1><p>No such user</p>' % username
+ return static .Data (text , 'text/html' )
+
+
+class FingerService (service .Service ):
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getFingerFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerProtocol
+ f .getUser = self .getUser
+ return f
+
+ def getResource (self ):
+ r = FingerResource (self .users )
+ return r
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , f .getFingerFactory ()
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (f .getResource ())
+ ).setServiceParent (serviceCollection )
+
+
+
+
Announce on IRC, Too
+
+
This is the first time there is client code. IRC clients often act a lot like
+servers: responding to events from the network. The reconnecting client factory
+will make sure that severed links will get re-established, with intelligent
+tweaked exponential back-off algorithms. The IRC client itself is simple: the
+only real hack is getting the nickname from the factory
+in connectionMade
.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .web import resource , server , static
+
+import cgi
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+
+class IRCReplyBot (irc .IRCClient ):
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ irc .IRCClient .msg (self , user , msg +': ' +message )
+ d .addCallback (writeResponse )
+
+
+class FingerService (service .Service ):
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getFingerFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerProtocol
+ f .getUser = self .getUser
+ return f
+
+ def getResource (self ):
+ r = resource .Resource ()
+ r .getChild = (lambda path , request :
+ static .Data ('<h1>%s</h1><p>%s</p>' %
+ tuple (map (cgi .escape ,
+ [path ,self .users .get (path ,
+ "No such user <p/> usage: site/user" )])),
+ 'text/html' ))
+ return r
+
+ def getIRCBot (self , nickname ):
+ f = protocol .ReconnectingClientFactory ()
+ f .protocol = IRCReplyBot
+ f .nickname = nickname
+ f .getUser = self .getUser
+ return f
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , f .getFingerFactory ()
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (f .getResource ())
+ ).setServiceParent (serviceCollection )
+internet .TCPClient ('irc.freenode.org' , 6667 , f .getIRCBot ('fingerbot' )
+ ).setServiceParent (serviceCollection )
+
+
+
FingerService
now has another new
+function, getIRCbot
, which returns
+the ReconnectingClientFactory
. This factory in turn will
+instantiate the IRCReplyBot
protocol. The IRCBot is
+configured in the last line to connect
+to irc.freenode.org
with a nickname
+of fingerbot
.
+
+
By
+overriding irc.IRCClient.connectionMade
, IRCReplyBot
+can access the nickname
attribute of the factory that
+instantiated it.
+
+
Add XML-RPC Support
+
+
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.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .web import resource , server , static , xmlrpc
+import cgi
+
+class FingerProtocol (basic .LineReceiver ):
+ def lineReceived (self , user ):
+ d = self .factory .getUser (user )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ self .transport .write (message + '\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeResponse )
+
+
+class IRCReplyBot (irc .IRCClient ):
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+
+ def onError (err ):
+ return 'Internal error in server'
+ d .addErrback (onError )
+
+ def writeResponse (message ):
+ irc .IRCClient .msg (self , user , msg +': ' +message )
+ d .addCallback (writeResponse )
+
+
+class FingerService (service .Service ):
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getFingerFactory (self ):
+ f = protocol .ServerFactory ()
+ f .protocol = FingerProtocol
+ f .getUser = self .getUser
+ return f
+
+ def getResource (self ):
+ r = resource .Resource ()
+ r .getChild = (lambda path , request :
+ static .Data ('<h1>%s</h1><p>%s</p>' %
+ tuple (map (cgi .escape ,
+ [path ,self .users .get (path , "No such user" )])),
+ 'text/html' ))
+ x = xmlrpc .XMLRPC ()
+ x .xmlrpc_getUser = self .getUser
+ r .putChild ('RPC2' , x )
+ return r
+
+ def getIRCBot (self , nickname ):
+ f = protocol .ReconnectingClientFactory ()
+ f .protocol = IRCReplyBot
+ f .nickname = nickname
+ f .getUser = self .getUser
+ return f
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , f .getFingerFactory ()
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (f .getResource ())
+ ).setServiceParent (serviceCollection )
+internet .TCPClient ('irc.freenode.org' , 6667 , f .getIRCBot ('fingerbot' )
+ ).setServiceParent (serviceCollection )
+
+
+
Instead of a web browser, we can test the XMLRPC finger using a simple
+client based on Python's built-in xmlrpclib
, which will access
+the resource we've made available at localhost/RPC2
.
+
+
1
+2
+3
+4
+5
+
+
+import xmlrpclib
+server = xmlrpclib .Server ('http://127.0.0.1:8000/RPC2' )
+print server .getUser ('moshez' )
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/style.html b/doc/core/howto/tutorial/style.html
new file mode 100644
index 0000000..8cc9969
--- /dev/null
+++ b/doc/core/howto/tutorial/style.html
@@ -0,0 +1,333 @@
+
+
+Twisted Documentation: The Evolution of Finger: cleaning up the finger code
+
+
+
+
+ The Evolution of Finger: cleaning up the finger code
+
+
+
+
+
+
Introduction
+
+
This is the third part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this section of the tutorial, we'll clean up our code so that it is
+closer to a readable and extensible style.
+
+
Write Readable Code
+
+
The last version of the application had a lot of hacks. We avoided
+sub-classing, didn't support things like user listings over the web,
+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.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , 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_GET (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 ):
+ if path =="" :
+ return UserStatusTree (self .service )
+ else :
+ 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_GET (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 .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+
+class FingerService (service .Service ):
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "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' , x )
+ return r
+
+ def getIRCBot (self , nickname ):
+ f = protocol .ReconnectingClientFactory ()
+ f .protocol = IRCReplyBot
+ f .nickname = nickname
+ f .getUser = self .getUser
+ return f
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , f .getFingerFactory ()
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (f .getResource ())
+ ).setServiceParent (serviceCollection )
+internet .TCPClient ('irc.freenode.org' , 6667 , f .getIRCBot ('fingerbot' )
+ ).setServiceParent (serviceCollection )
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/tutorial/web.html b/doc/core/howto/tutorial/web.html
new file mode 100644
index 0000000..fca15b7
--- /dev/null
+++ b/doc/core/howto/tutorial/web.html
@@ -0,0 +1,610 @@
+
+
+Twisted Documentation: The Evolution of Finger: a web frontend
+
+
+
+
+ The Evolution of Finger: a web frontend
+
+
+
+
+
+
Introduction
+
+
This is the sixth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger .
+
+
In this part, we demonstrate adding a web frontend using
+simple twisted.web.resource.Resource
+objects: UserStatusTree
, which will
+produce a listing of all users at the base URL (/
) of our
+site; UserStatus
, which gives the status
+of each user at the location /username
;
+and UserStatusXR
, which exposes an XMLRPC
+interface to getUser
+and getUsers
functions at the
+URL /RPC2
.
+
+
In this example we construct HTML segments manually. If the web interface
+was less trivial, we would want to use more sophisticated web templating and
+design our system so that HTML rendering and logic were clearly separated.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+
+from twisted .application import internet , service
+from twisted .internet import protocol , reactor , defer
+from twisted .words .protocols import irc
+from twisted .protocols import basic
+from twisted .python import components
+from twisted .web import resource , server , static , xmlrpc , microdom
+from zope .interface import Interface , implements
+import cgi
+
+class IFingerService (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def getUsers ():
+ """
+ Return a deferred returning a list of strings.
+ """
+
+
+class IFingerSetterService (Interface ):
+
+ def setUser (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 +'\r\n' )
+ self .transport .loseConnection ()
+ d .addCallback (writeValue )
+
+
+class IFingerFactory (Interface ):
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerFactory )
+
+
+class FingerSetterProtocol (basic .LineReceiver ):
+
+ def connectionMade (self ):
+ self .lines = []
+
+ def lineReceived (self , line ):
+ self .lines .append (line )
+
+ def connectionLost (self , reason ):
+ if len (self .lines ) == 2 :
+ self .factory .setUser (*self .lines )
+
+
+class IFingerSetterFactory (Interface ):
+
+ def setUser (user , status ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 ,
+ IFingerSetterService ,
+ IFingerSetterFactory )
+
+
+class IRCReplyBot (irc .IRCClient ):
+
+ def connectionMade (self ):
+ self .nickname = self .factory .nickname
+ irc .IRCClient .connectionMade (self )
+
+ def privmsg (self , user , channel , msg ):
+ user = user .split ('!' )[0 ]
+ if self .nickname .lower () == channel .lower ():
+ d = self .factory .getUser (msg )
+ d .addErrback (catchError )
+ d .addCallback (lambda m : "Status of %s: %s" % (msg , m ))
+ d .addCallback (lambda m : self .msg (user , m ))
+
+
+class IIRCClientFactory (Interface ):
+
+ """
+ @ivar nickname
+ """
+
+ def getUser (user ):
+ """
+ Return a deferred returning a string.
+ """
+
+ def buildProtocol (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 (user )
+
+components .registerAdapter (IRCClientFactoryFromService ,
+ IFingerService ,
+ IIRCClientFactory )
+
+
+class UserStatusTree (resource .Resource ):
+
+ def __init__ (self , service ):
+ resource .Resource .__init__ (self )
+ self .service =service
+
+
+ self .putChild ("RPC2" , UserStatusXR (self .service ))
+
+
+ self .putChild ("" , self )
+
+ def _cb_render_GET (self , users , request ):
+ userOutput = '' .join (["<li><a href=\"%s\">%s</a></li>" % (user , user )
+ for user in users ])
+ request .write ("""
+ <html><head><title>Users</title></head><body>
+ <h1>Users</h1>
+ <ul>
+ %s
+ </ul></body></html>""" % userOutput )
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUsers ()
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+ def getChild (self , path , request ):
+ return UserStatus (user =path , service =self .service )
+
+components .registerAdapter (UserStatusTree , IFingerService , resource .IResource )
+
+
+class UserStatus (resource .Resource ):
+
+ def __init__ (self , user , service ):
+ resource .Resource .__init__ (self )
+ self .user = user
+ self .service = service
+
+ def _cb_render_GET (self , status , request ):
+ request .write ("""<html><head><title>%s</title></head>
+ <body><h1>%s</h1>
+ <p>%s</p>
+ </body></html>""" % (self .user , self .user , status ))
+ request .finish ()
+
+ def render_GET (self , request ):
+ d = self .service .getUser (self .user )
+ d .addCallback (self ._cb_render_GET , request )
+
+
+ return server .NOT_DONE_YET
+
+
+class UserStatusXR (xmlrpc .XMLRPC ):
+
+ def __init__ (self , service ):
+ xmlrpc .XMLRPC .__init__ (self )
+ self .service = service
+
+ def xmlrpc_getUser (self , user ):
+ return self .service .getUser (user )
+
+ def xmlrpc_getUsers (self ):
+ return self .service .getUsers ()
+
+
+class FingerService (service .Service ):
+
+ implements (IFingerService )
+
+ def __init__ (self , filename ):
+ self .filename = filename
+ self .users = {}
+
+ def _read (self ):
+ self .users .clear ()
+ for line in file (self .filename ):
+ user , status = line .split (':' , 1 )
+ user = user .strip ()
+ status = status .strip ()
+ self .users [user ] = status
+ self .call = reactor .callLater (30 , self ._read )
+
+ def getUser (self , user ):
+ return defer .succeed (self .users .get (user , "No such user" ))
+
+ def getUsers (self ):
+ return defer .succeed (self .users .keys ())
+
+ def startService (self ):
+ self ._read ()
+ service .Service .startService (self )
+
+ def stopService (self ):
+ service .Service .stopService (self )
+ self .call .cancel ()
+
+
+application = service .Application ('finger' , uid =1 , gid =1 )
+f = FingerService ('/etc/users' )
+serviceCollection = service .IServiceCollection (application )
+f .setServiceParent (serviceCollection )
+internet .TCPServer (79 , IFingerFactory (f )
+ ).setServiceParent (serviceCollection )
+internet .TCPServer (8000 , server .Site (resource .IResource (f ))
+ ).setServiceParent (serviceCollection )
+i = IIRCClientFactory (f )
+i .nickname = 'fingerbot'
+internet .TCPClient ('irc.freenode.org' , 6667 , i
+ ).setServiceParent (serviceCollection )
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/udp.html b/doc/core/howto/udp.html
new file mode 100644
index 0000000..756d3d1
--- /dev/null
+++ b/doc/core/howto/udp.html
@@ -0,0 +1,304 @@
+
+
+Twisted Documentation: UDP Networking
+
+
+
+
+ UDP Networking
+
+
+
+
+
Overview
+
+
Unlike TCP, UDP has no notion of connections. A UDP socket can receive
+ datagrams from any server on the network and send datagrams to any host on
+ the network. In addition, datagrams may arrive in any order, never arrive at
+ all, or be duplicated in transit.
+
+
Since there are no connections, we only use a single object, a protocol,
+ for each UDP socket. We then use the reactor to connect this protocol to a
+ UDP transport, using the
+ twisted.internet.interfaces.IReactorUDP
+ reactor API.
+
+
DatagramProtocol
+
+
The class where you actually implement the protocol parsing and handling
+ will usually be descended
+ from twisted.internet.protocol.DatagramProtocol
or
+ from one of its convenience children. The DatagramProtocol
+ class receives datagrams and can send them out over the network. Received
+ datagrams include the address they were sent from. When sending datagrams
+ the destination address must be specified.
+
+
Here is a simple example:
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+
from twisted .internet .protocol import DatagramProtocol
+from twisted .internet import reactor
+
+class Echo (DatagramProtocol ):
+
+ def datagramReceived (self , data , (host , port )):
+ print "received %r from %s:%d" % (data , host , port )
+ self .transport .write (data , (host , port ))
+
+reactor .listenUDP (9999 , Echo ())
+reactor .run ()
+
+
+
As you can see, the protocol is registered with the reactor. This means
+ it may be persisted if it's added to an application, and thus it has
+ startProtocol
+ and stopProtocol
+ methods that will get called when the protocol is connected and disconnected
+ from a UDP socket.
+
+
The protocol's transport
attribute will
+ implement the twisted.internet.interfaces.IUDPTransport
interface.
+ Notice that the host
argument should be an
+ IP address, not a hostname. If you only have the hostname use reactor.resolve()
to resolve the address (see twisted.internet.interfaces.IReactorCore.resolve
).
+
+
+
Connected UDP
+
+
A connected UDP socket is slightly different from a standard one - it
+ can only send and receive datagrams to/from a single address, but this
+ does not in any way imply a connection. Datagrams may still arrive in any
+ order, and the port on the other side may have no one listening. The
+ benefit of the connected UDP socket is that it it may
+ provide notification of undelivered packages. This depends on many
+ factors, almost all of which are out of the control of the application,
+ but it still presents certain benefits which occasionally make it
+ useful.
+
+
Unlike a regular UDP protocol, we do not need to specify where to send
+ datagrams and are not told where they came from since they can only come
+ from the address to which the socket is 'connected'.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
from twisted .internet .protocol import DatagramProtocol
+from twisted .internet import reactor
+
+class Helloer (DatagramProtocol ):
+
+ def startProtocol (self ):
+ host = "192.168.1.1"
+ port = 1234
+
+ self .transport .connect (host , port )
+ print "now we can only send to host %s port %d" % (host , port )
+ self .transport .write ("hello" )
+
+ def datagramReceived (self , data , (host , port )):
+ print "received %r from %s:%d" % (data , host , port )
+
+
+
+ def connectionRefused (self ):
+ print "No one listening"
+
+
+reactor .listenUDP (0 , Helloer ())
+reactor .run ()
+
+
+
Note that connect()
,
+ like write()
will only accept IP addresses, not
+ unresolved hostnames. To obtain the IP of a hostname
+ use reactor.resolve()
, e.g.:
+
+
1
+2
+3
+4
+5
+6
+7
+8
+
from twisted .internet import reactor
+
+def gotIP (ip ):
+ print "IP of 'example.com' is" , ip
+ reactor .callLater (3 , reactor .stop )
+
+reactor .resolve ('example.com' ).addCallback (gotIP )
+reactor .run ()
+
+
+
Connecting to a new address after a previous connection or making a
+ connected port unconnected are not currently supported, but likely will be
+ in the future.
+
+
Multicast UDP
+
+
Multicast allows a process to contact multiple hosts with a single
+ packet, without knowing the specific IP address of any of the hosts. This
+ is in contrast to normal, or unicast, UDP, where each datagram has a single
+ IP as its destination. Multicast datagrams are sent to special multicast
+ group addresses (in the IPv4 range 224.0.0.0 to 239.255.255.255), along with
+ a corresponding port. In order to receive multicast datagrams, you must
+ join that specific group address. However, any UDP socket can send to
+ multicast addresses.
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
from twisted .internet .protocol import DatagramProtocol
+from twisted .internet import reactor
+
+
+class MulticastPingPong (DatagramProtocol ):
+
+ def startProtocol (self ):
+ """
+ Called after protocol has started listening.
+ """
+
+ self .transport .setTTL (5 )
+
+ self .transport .joinGroup ("228.0.0.5" )
+
+ def datagramReceived (self , datagram , address ):
+ print "Datagram %s received from %s" % (repr (datagram ), repr (address ))
+ if datagram == "Client: Ping" :
+
+
+ self .transport .write ("Server: Pong" , address )
+
+
+
+
+reactor .listenMulticast (8005 , MulticastPingPong (),
+ listenMultiple =True )
+reactor .run ()
+
+
+
As with UDP, with multicast there is no server/client differentiation
+ at the protocol level. Our server example is very simple and closely
+ resembles a normal listenUDP
+ protocol implementation. The main difference is that instead
+ of listenUDP
, listenMulticast
+ is called with the port number. The server calls joinGroup
to
+ join a multicast group. A DatagramProtocol
+ that is listening with multicast and has joined a group can receive
+ multicast datagrams, but also unicast datagrams sent directly to its
+ address. The server in the example above sends such a unicast message in
+ reply to the multicast message it receives from the client.
+
+
+
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
from twisted .internet .protocol import DatagramProtocol
+from twisted .internet import reactor
+
+
+class MulticastPingClient (DatagramProtocol ):
+
+ def startProtocol (self ):
+
+ self .transport .joinGroup ("228.0.0.5" )
+
+
+ self .transport .write ('Client: Ping' , ("228.0.0.5" , 8005 ))
+
+ def datagramReceived (self , datagram , address ):
+ print "Datagram %s received from %s" % (repr (datagram ), repr (address ))
+
+
+reactor .listenMulticast (8005 , MulticastPingClient (), listenMultiple =True )
+reactor .run ()
+
+
+
Note that a multicast socket will have a default TTL (time to live) of
+ 1. That is, datagrams won't traverse more than one router hop, unless a
+ higher TTL is set with
+ setTTL
. Other
+ functionality provided by the multicast transport
+ includes setOutgoingInterface
+ and setLoopbackMode
+ -- see IMulticastTransport
for more
+ information.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/howto/vision.html b/doc/core/howto/vision.html
new file mode 100644
index 0000000..cd4997d
--- /dev/null
+++ b/doc/core/howto/vision.html
@@ -0,0 +1,43 @@
+
+
+Twisted Documentation: The Vision For Twisted
+
+
+
+
+ The Vision For Twisted
+
+
+
+
+
Many other documents in this repository are dedicated to
+ defining what Twisted is. Here, I will attempt to explain not
+ what Twisted is, but what it should be, once I've met my goals
+ with it.
+
+
First, Twisted should be fun. It began as a game, it is
+ being used commercially in games, and it will be, I hope, an
+ interactive and entertaining experience for the end-user.
+
+
Twisted is a platform for developing internet applications.
+ While Python by itself is a very powerful language, there are
+ many facilities it lacks which other languages have spent great
+ attention to adding. It can do this now; Twisted is a good (if
+ somewhat idiosyncratic) pure-python framework or library,
+ depending on how you treat it, and it continues to improve.
+
+
As a platform, Twisted should be focused on integration.
+ Ideally, all functionality will be accessible through all
+ protocols. Failing that, all functionality should be
+ configurable through at least one protocol, with a seamless and
+ consistent user-interface. The next phase of development will
+ be focusing strongly on a configuration system which will unify
+ many disparate pieces of the current infrastructure, and allow
+ them to be tacked together by a non-programmer.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/img/TwistedLogo.bmp b/doc/core/img/TwistedLogo.bmp
new file mode 100644
index 0000000..940ede0
Binary files /dev/null and b/doc/core/img/TwistedLogo.bmp differ
diff --git a/doc/core/img/cred-login.dia b/doc/core/img/cred-login.dia
new file mode 100644
index 0000000..f9dfaa7
Binary files /dev/null and b/doc/core/img/cred-login.dia differ
diff --git a/doc/core/img/cred-login.png b/doc/core/img/cred-login.png
new file mode 100644
index 0000000..a27dff4
Binary files /dev/null and b/doc/core/img/cred-login.png differ
diff --git a/doc/core/img/deferred-attach.dia b/doc/core/img/deferred-attach.dia
new file mode 100644
index 0000000..9e42967
Binary files /dev/null and b/doc/core/img/deferred-attach.dia differ
diff --git a/doc/core/img/deferred-attach.png b/doc/core/img/deferred-attach.png
new file mode 100644
index 0000000..8050058
Binary files /dev/null and b/doc/core/img/deferred-attach.png differ
diff --git a/doc/core/img/deferred-process.dia b/doc/core/img/deferred-process.dia
new file mode 100644
index 0000000..37c5dd3
Binary files /dev/null and b/doc/core/img/deferred-process.dia differ
diff --git a/doc/core/img/deferred-process.png b/doc/core/img/deferred-process.png
new file mode 100644
index 0000000..d4047eb
Binary files /dev/null and b/doc/core/img/deferred-process.png differ
diff --git a/doc/core/img/deferred-states.svg b/doc/core/img/deferred-states.svg
new file mode 100644
index 0000000..cc8e8da
--- /dev/null
+++ b/doc/core/img/deferred-states.svg
@@ -0,0 +1,3 @@
+
+
+2010-02-21 23:40Z Canvas 1 Layer 1 Unfi red No Canceller Synchronous Result Synchronous Error callback() errback() cancel() invoke user code added with addCallback that returns value invoke callback that returns value callback() (raise AlreadyCalledError) Synchronous Error + Suppress AlreadyCalled errback() (no-op, discard other error) callback() (no-op, discard value) cancel() Unfi red With Canceller Does Not Exist Deferred() Deferred(canceller=myFunction) cancel() (no-op) cancel() (no-op) W aiting on another Deferred invoke user code that returns a Deferred invoke user code that returns a Deferred cancel() sub-deferred gives a result sub-deferred gives an error sub-deferred waits on another deferred cancel() (no-op) callback() errback() cancel() (with canceller that calls callback()) invoke user code that returns Failure / raises Exception invoke user code that returns Failure / raises Exception
diff --git a/doc/core/img/deferred.dia b/doc/core/img/deferred.dia
new file mode 100644
index 0000000..f27410a
Binary files /dev/null and b/doc/core/img/deferred.dia differ
diff --git a/doc/core/img/deferred.png b/doc/core/img/deferred.png
new file mode 100644
index 0000000..069d1d5
Binary files /dev/null and b/doc/core/img/deferred.png differ
diff --git a/doc/core/index.html b/doc/core/index.html
new file mode 100644
index 0000000..37b8b1d
--- /dev/null
+++ b/doc/core/index.html
@@ -0,0 +1,31 @@
+
+
+Twisted Documentation: Twisted Core Documentation
+
+
+
+
+ Twisted Core Documentation
+
+
+
+
+
+
An API
+ reference is available on the twistedmatrix web site.
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/manhole-man.html b/doc/core/man/manhole-man.html
new file mode 100644
index 0000000..68b6e64
--- /dev/null
+++ b/doc/core/man/manhole-man.html
@@ -0,0 +1,50 @@
+
+
+Twisted Documentation: MANHOLE.1
+
+
+
+
+ MANHOLE.1
+
+
+
+
+
+
NAME
+
+
manhole - Connect to a Twisted Manhole service
+
+
+
SYNOPSIS
+
+
manhole
+
+
DESCRIPTION
+
+
manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+
+
+
AUTHOR
+
+
Written by Chris Armstrong, copied from Moshe Zadka's faucet manpage.
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/manhole.1 b/doc/core/man/manhole.1
new file mode 100644
index 0000000..3d78617
--- /dev/null
+++ b/doc/core/man/manhole.1
@@ -0,0 +1,16 @@
+.TH MANHOLE "1" "August 2001" "" ""
+.SH NAME
+manhole \- Connect to a Twisted Manhole service
+.SH SYNOPSIS
+.B manhole
+.SH DESCRIPTION
+manhole is a GTK interface to Twisted Manhole services. You can execute python code as if at an interactive Python console inside a running Twisted process with this.
+.SH AUTHOR
+Written by Chris Armstrong, copied from Moshe Zadka's "faucet" manpage.
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/pyhtmlizer-man.html b/doc/core/man/pyhtmlizer-man.html
new file mode 100644
index 0000000..ad6a6b0
--- /dev/null
+++ b/doc/core/man/pyhtmlizer-man.html
@@ -0,0 +1,51 @@
+
+
+Twisted Documentation: pyhtmlizer.1
+
+
+
+
+ pyhtmlizer.1
+
+
+
+
+
+
NAME
+
+
pyhtmlizer - pretty-print Python source as HTML
+
+
+
+
SYNTAX
+
+
pyhtmlizer [-s|--stylesheet <url >] <filename >
+
+
+
DESCRIPTION
+
+
This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+
+
+
OPTIONS
+
+
--stylesheet, -s <url >
+Links to the stylesheet at <url >.
+
+
+--help
+Output help information and exit.
+
+
+-v , --version
+Output version information and exit.
+
+
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/pyhtmlizer.1 b/doc/core/man/pyhtmlizer.1
new file mode 100644
index 0000000..9621e60
--- /dev/null
+++ b/doc/core/man/pyhtmlizer.1
@@ -0,0 +1,22 @@
+.TH "pyhtmlizer" "1" "" "Twisted Matrix Laboratories" ""
+.SH "NAME"
+.LP
+pyhtmlizer \- pretty\-print Python source as HTML
+
+.SH "SYNTAX"
+.LP
+pyhtmlizer [\fI\-s|\-\-stylesheet\fR <\fIurl\fR>] <\fIfilename\fR>
+.SH "DESCRIPTION"
+.LP
+This generates a HTML document with Python source marked up with span elements. To colorize, provide a stylesheet.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-\-stylesheet, \-s\fR <\fIurl\fR>
+Links to the stylesheet at <\fIurl\fR>.
+.TP
+\fB\-\-help\fR
+Output help information and exit.
+.TP
+\fB\-v\fR, \fB\--version\fR
+Output version information and exit.
diff --git a/doc/core/man/tap2deb-man.html b/doc/core/man/tap2deb-man.html
new file mode 100644
index 0000000..d42b2a0
--- /dev/null
+++ b/doc/core/man/tap2deb-man.html
@@ -0,0 +1,101 @@
+
+
+Twisted Documentation: TAP2DEB.1
+
+
+
+
+ TAP2DEB.1
+
+
+
+
+
+
NAME
+
+
tap2deb - create Debian packages which wrap .tap files
+
+
+
SYNOPSIS
+
+
tap2deb [options]
+
+
+
DESCRIPTION
+
+
Create a ready to upload Debian package in .build
+
-u , --unsigned
+do not sign the Debian package
+
+
+-t , --tapfile <tapfile>
+Build the application around the given .tap (default twistd.tap)
+
+
+-y , --type <type>
+The configuration has the given type . Allowable types are
+tap , source , xml and python .
+The first three types are mktap output formats,
+while the last one is a manual building of application
+(see twistd(1) , the -y option).
+
+
+-p , --protocol <protocol>
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+
+
+-d , --debfile <debfile>
+The name of the debian package. Default is 'twisted-'+protocol.
+
+
+-V , --set-version <version>
+The version of the Debian package. The default is 1.0
+
+
+-e , --description <description>
+The one-line description. Default is uninteresting.
+
+
+-l , --long_description <long_description>
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+
+
+-m , --maintainer <maintainer>
+The maintainer, as Name Lastname <email address> . This will
+go in the meta-files, as well as be used as the id to sign the package.
+
+
+--version
+Output version information and exit.
+
+
+
+
+
+
+
AUTHOR
+
+
Written by Moshe Zadka, based on twistd's help messages
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2000-2008 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/tap2deb.1 b/doc/core/man/tap2deb.1
new file mode 100644
index 0000000..de52a70
--- /dev/null
+++ b/doc/core/man/tap2deb.1
@@ -0,0 +1,55 @@
+.TH TAP2DEB "1" "July 2001" "" ""
+.SH NAME
+tap2deb \- create Debian packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2deb
+[options]
+.SH DESCRIPTION
+Create a ready to upload Debian package in ".build"
+.TP
+\fB\-u\fR, \fB\--unsigned\fR
+do not sign the Debian package
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--debfile\fR \fI\fR
+The name of the debian package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI\fR
+The version of the Debian package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2deb.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI\fR
+The maintainer, as "Name Lastname ". This will
+go in the meta-files, as well as be used as the id to sign the package.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/tap2rpm-man.html b/doc/core/man/tap2rpm-man.html
new file mode 100644
index 0000000..8d4535f
--- /dev/null
+++ b/doc/core/man/tap2rpm-man.html
@@ -0,0 +1,100 @@
+
+
+Twisted Documentation: TAP2RPM.1
+
+
+
+
+ TAP2RPM.1
+
+
+
+
+
+
NAME
+
+
tap2rpm - create RPM packages which wrap .tap files
+
+
+
SYNOPSIS
+
+
tap2rpm [options]
+
+
+
DESCRIPTION
+
+
Create a set of RPM/SRPM packages in the current directory
+
-t , --tapfile <tapfile>
+Build the application around the given .tap (default twistd.tap)
+
+
+-y , --type <type>
+The configuration has the given type . Allowable types are
+tap , source , xml and python .
+The first three types are mktap output formats,
+while the last one is a manual building of application
+(see twistd(1) , the -y option).
+
+
+-p , --protocol <protocol>
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+
+
+-d , --rpmfile <rpmfile>
+The name of the RPM package. Default is 'twisted-'+protocol.
+
+
+-V , --set-version <version>
+The version of the RPM package. The default is 1.0
+
+
+-e , --description <description>
+The one-line description. Default is uninteresting.
+
+
+-l , --long_description <long_description>
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+
+
+-m , --maintainer <maintainer>
+The maintainer, as Name Lastname <email address> . This will
+go in the meta-files.
+
+
+--version
+Output version information and exit.
+
+
+
+
+
+
+
AUTHOR
+
+
tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+
+
+
REPORTING BUGS
+
+
To report a bug, visit
+http://twistedmatrix.com/trac/wiki/TwistedDevelopment#FilingTickets for more
+information.
+
+
+
COPYRIGHT
+
+
Copyright © 2000-2009 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/tap2rpm.1 b/doc/core/man/tap2rpm.1
new file mode 100644
index 0000000..6d53c9d
--- /dev/null
+++ b/doc/core/man/tap2rpm.1
@@ -0,0 +1,55 @@
+.TH TAP2RPM "1" "July 2001" "" ""
+.SH NAME
+tap2rpm \- create RPM packages which wrap .tap files
+.SH SYNOPSIS
+.B tap2rpm
+[options]
+.SH DESCRIPTION
+Create a set of RPM/SRPM packages in the current directory
+.TP
+\fB\-t\fR, \fB\--tapfile\fR \fI\fR
+Build the application around the given .tap (default twistd.tap)
+.TP
+\fB\-y\fR, \fB\--type\fR \fI\fR
+The configuration has the given type . Allowable types are
+\fBtap\fR, \fBsource\fR, \fBxml\fR and \fBpython\fR.
+The first three types are \fBmktap\fR output formats,
+while the last one is a manual building of application
+(see \fBtwistd(1)\fR, the \fB\-y\fR option).
+.TP
+\fB\-p\fR, \fB\--protocol\fR \fI\fR
+The name of the protocol this will be used to serve. This is intended
+as a part of the description. Default is the name of the tapfile, minus
+any extensions.
+.TP
+\fB\-d\fR, \fB\--rpmfile\fR \fI\fR
+The name of the RPM package. Default is 'twisted-'+protocol.
+.TP
+\fB\-V\fR, \fB\--set-version\fR \fI\fR
+The version of the RPM package. The default is 1.0
+.TP
+\fB\-e\fR, \fB\--description\fR \fI\fR
+The one-line description. Default is uninteresting.
+.TP
+\fB\-l\fR, \fB\--long_description\fR \fI\fR
+A multi-line description. Default is explanation about
+this being an automatic package created from tap2rpm.
+.TP
+\fB\-m\fR, \fB\--maintainer\fR \fI\fR
+The maintainer, as "Name Lastname ". This will
+go in the meta-files.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
+This man page is heavily based on the tap2deb man page by Moshe Zadka.
+.SH "REPORTING BUGS"
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#FilingTickets\fR for more
+information.
+.SH COPYRIGHT
+Copyright \(co 2000-2009 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/tapconvert-man.html b/doc/core/man/tapconvert-man.html
new file mode 100644
index 0000000..20c979b
--- /dev/null
+++ b/doc/core/man/tapconvert-man.html
@@ -0,0 +1,82 @@
+
+
+Twisted Documentation: TAPCONVERT.1
+
+
+
+
+ TAPCONVERT.1
+
+
+
+
+
+
NAME
+
+
tapconvert - convert Twisted configurations from one format to another
+
+
+
SYNOPSIS
+
+
tapconvert -i input -o output [-f input-type ] [-t output-type ] [-d] [-e]
+
+
tapconvert --help
+
+
DESCRIPTION
+
+
The --help prints out a usage message to standard output.
+
--in , -i <input file>
+The name of the input configuration.
+
+
+--out , -o <output file>
+The name of the output configuration.
+
+
+--typein , -f <input type>
+The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+
+
+--typeout , -t <output type>
+The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+
+
+--decrypt , -d
+Decrypt the specified tap/aos/xml input file.
+
+
+--encrypt , -e
+Encrypt output file before writing.
+
+
+--version
+Output version information and exit.
+
+
+
+
+
+
+
AUTHOR
+
+
Written by Moshe Zadka, based on tapconvert's help messages
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/bugs/
+
+
+
COPYRIGHT
+
+
Copyright © 2000-2012 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/tapconvert.1 b/doc/core/man/tapconvert.1
new file mode 100644
index 0000000..d37bfac
--- /dev/null
+++ b/doc/core/man/tapconvert.1
@@ -0,0 +1,40 @@
+.TH TAPCONVERT "1" "July 2001" "" ""
+.SH NAME
+tapconvert \- convert Twisted configurations from one format to another
+.SH SYNOPSIS
+.B tapconvert -i \fIinput\fR -o \fIoutput\fR [-f \fIinput-type\fR] [-t \fIoutput-type\fR] [-d] [-e]
+.PP
+.B tapconvert --help
+.SH DESCRIPTION
+.PP
+The \fB\--help\fR prints out a usage message to standard output.
+.TP
+\fB\--in\fR, \fB\-i\fR \fI \fR
+The name of the input configuration.
+.TP
+\fB\--out\fR, \fB\-o\fR \fI\fR
+The name of the output configuration.
+.TP
+\fB\--typein\fR, \fB\-f\fR \fI \fR
+The type of the input file. Can be either 'guess', 'python', 'pickle', 'xml', or 'source'. Default is 'guess'.
+.TP
+\fB\--typeout\fR, \fB\-t\fR \fI\fR
+The type of the output file. Can be either 'pickle', 'xml', or 'source'. Default is 'source'.
+.TP
+\fB\--decrypt\fR, \fB\-d\fR
+Decrypt the specified tap/aos/xml input file.
+.TP
+\fB\--encrypt\fR, \fB\-e\fR
+Encrypt output file before writing.
+.TP
+\fB\--version\fR
+Output version information and exit.
+.SH AUTHOR
+Written by Moshe Zadka, based on tapconvert's help messages
+.SH "REPORTING BUGS"
+To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+.SH COPYRIGHT
+Copyright \(co 2000-2012 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/trial-man.html b/doc/core/man/trial-man.html
new file mode 100644
index 0000000..7707c6b
--- /dev/null
+++ b/doc/core/man/trial-man.html
@@ -0,0 +1,275 @@
+
+
+Twisted Documentation: TRIAL.1
+
+
+
+
+ TRIAL.1
+
+
+
+
+
+
NAME
+
+
trial - run unit tests
+
+
+
SYNOPSIS
+
+
trial [ options ] [ file | package | module | TestCase | testmethod ] ...
+
+
+
trial --help | -h
+
+
+
DESCRIPTION
+
+
trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+
+
+
trial will take either filenames or fully qualified Python names as
+arguments. Thus `trial myproject/foo.py', `trial myproject.foo' and
+`trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+
+
+
After running the given test suite, the default test reporter prints a summary
+of the test run. This consists of the word PASSED (if all tests ran as
+expected) or FAILED (if any test behaved unexpectedly) followed by a count of
+the different kinds of test results encountered. The possible kinds of test
+results includes:
+
successes
+ Tests that passed all their assertions and completed without error.
+These are marked PASSED in the normal test output.
+
+
+failures
+ Tests that failed an assertion, called self.fail() or explicitly raised
+self.failureException for some reason. These are marked FAILED in the
+normal test output.
+
+
+errors
+ Tests that raised an unexpected exception (including AssertionError),
+tests that caused the tearDown() method to raise an exception, tests
+that run for longer than the timeout interval, tests that caused
+something to call twisted.python.log.err() without subsequently calling
+self.flushLoggedErrors(), tests that leave the reactor in an unclean
+state, etc. These are marked ERROR in the normal test output.
+Note that because errors can be caused after the actual test method
+returns, it is possible for a single test to be reported as both an
+error and a failure, and hence the total number of test results can be
+greater than the total number of tests executed.
+
+
+skips
+ Tests that were skipped, usually because of missing dependencies. These
+are marked SKIPPED in the normal test output.
+
+
+expectedFailures
+ Tests that failed, but were expected to fail, usually because the test
+is for a feature that hasn't been implemented yet. These are marked
+TODO in the normal test output.
+
+
+unexpectedSuccesses
+ Tests that should have been listed under expectedFailures, except that
+for some reason the test succeeded. These are marked SUCCESS!?! in
+the normal test output.
+
+
+
+
+
+
+
OPTIONS
+
+
-b , --debug
+Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load `.pdbrc' from current directory if
+it exists.
+
+
+-B , --debug-stacktraces
+Report Deferred creation and callback stack traces
+
+
+--coverage
+Generate coverage information in the `coverage' subdirectory of the trial temp
+directory (`_trial_temp' by default). For each Python module touched by the
+execution of the given tests, a file will be created in the coverage directory
+named for the module's fully-qualified name with the suffix `.cover'. For
+example, because the trial test runner is written in Python, the coverage
+directory will almost always contain a file named `twisted.trial.runner.cover'.
+
+Each `.cover' file contains a copy of the Python source of the module in
+question, with a prefix at the beginning of each line containing coverage
+information. For lines that are not executable (blank lines, comments, etc.)
+the prefix is blank. For executable lines that were run in the course of the
+test suite, the prefix is a number indicating the number of times that line was
+executed. The string `>>>>>>' prefixes executable lines that were not executed
+in the course of the test suite.
+
+Note that this functionality uses Python's sys.settrace() function, so tests
+that call sys.settrace() themselves are likely to break trial's coverage
+functionality.
+
+
+--disablegc
+Disable the garbage collector for the duration of the test run. As each test is
+run, trial saves the TestResult objects, which means that Python's garbage
+collector has more non-garbage objects to wade through, making each
+garbage-collection run slightly slower. Disabling garbage collection entirely
+will make some test suites complete faster (contrast --force-gc, below), at the
+cost of increasing (possibly greatly) memory consumption. This option also makes
+tests slightly more deterministic, which might help debugging in extreme
+circumstances.
+
+
+-e , --rterrors
+Print tracebacks to standard output as soon as they occur
+
+
+--force-gc
+Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+
+
+-h , --help
+Print a usage message to standard output, then exit.
+
+
+--help-reporters
+Print a list of valid reporters to standard output, then exit. Reporters can
+be selected with the --reporter option described below.
+
+
+--help-reactors
+Print a list of possible reactors to standard output, then exit. Not all listed
+reactors are available on every platform. Reactors can be selected with the
+--reactor option described below.
+
+
+-l , --logfile logfile
+Direct the log to a different file. The default file is `test.log'.
+logfile is relative to _trial_temp.
+
+
+-n , --dry-run
+Go through all the tests and make them pass without running.
+
+
+-N , --no-recurse
+By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+
+
+--nopm
+Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+
+
+--profile
+Run tests under the Python profiler.
+
+
+-r , --reactor reactor
+Choose which reactor to use. See --help-reactors for a list.
+
+
+--recursionlimit
+Set Python's recursion limit. See sys.setrecursionlimit()
+
+
+--reporter
+Select the reporter to use for trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+
+
+--spew
+Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+
+
+--tbformat format
+Format to display tracebacks with. Acceptable values are `default', `brief'
+and `verbose'. `brief' produces tracebacks that play nicely with Emacs' GUD.
+
+
+--temp-directory directory
+WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first deletes this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will delete the directory before re-creating it.
+
+
+--testmodule filename
+Ask trial to look into filename and run any tests specified using the
+Emacs-style buffer variable `test-case-name'.
+
+
+--unclean-warnings
+As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+
+
+-u , --until-failure
+Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+
+
+--version
+Prints the Twisted version number and exit.
+
+
+--without-module modulenames
+Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+
+
+-z , --random [seed ]
+Run the tests in random order using the specified seed.
+
+
+
+
+
SEE ALSO
+
+
The latest version of the trial documentation can be found at
+http://twistedmatrix.com/documents/current/core/howto/testing.html
+
+
+
AUTHOR
+
+
Written by Jonathan M. Lange
+
+
+
REPORTING BUGS
+
+
To report a bug, visit http://twistedmatrix.com/trac/newticket
+
+
+
COPYRIGHT
+
+
Copyright © 2003-2011 Twisted Matrix Laboratories
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/trial.1 b/doc/core/man/trial.1
new file mode 100644
index 0000000..f01934d
--- /dev/null
+++ b/doc/core/man/trial.1
@@ -0,0 +1,200 @@
+.TH TRIAL "1" "Oct 2007" "" ""
+.SH NAME
+trial \- run unit tests
+.SH SYNOPSIS
+\fBtrial\fR [ \fIoptions\fR ] [ \fIfile\fR | \fIpackage\fR | \fImodule\fR | \fITestCase\fR | \fItestmethod\fR ] ...
+.PP
+\fBtrial --help\fR | \fB-h\fR
+.SH DESCRIPTION
+.PP
+trial loads and executes a suite of unit tests, obtained from modules,
+packages and files listed on the command line.
+.PP
+trial will take either filenames or fully qualified Python names as
+arguments. Thus `trial myproject/foo.py', `trial myproject.foo' and
+`trial myproject.foo.SomeTestCase.test_method' are all valid ways to
+invoke trial.
+.PP
+After running the given test suite, the default test reporter prints a summary
+of the test run. This consists of the word "PASSED" (if all tests ran as
+expected) or "FAILED" (if any test behaved unexpectedly) followed by a count of
+the different kinds of test results encountered. The possible kinds of test
+results includes:
+.TP
+successes
+Tests that passed all their assertions and completed without error.
+These are marked "PASSED" in the normal test output.
+.TP
+failures
+Tests that failed an assertion, called self.fail() or explicitly raised
+self.failureException for some reason. These are marked "FAILED" in the
+normal test output.
+.TP
+errors
+Tests that raised an unexpected exception (including AssertionError),
+tests that caused the tearDown() method to raise an exception, tests
+that run for longer than the timeout interval, tests that caused
+something to call twisted.python.log.err() without subsequently calling
+self.flushLoggedErrors(), tests that leave the reactor in an unclean
+state, etc. These are marked "ERROR" in the normal test output.
+.IP
+Note that because errors can be caused after the actual test method
+returns, it is possible for a single test to be reported as both an
+error and a failure, and hence the total number of test results can be
+greater than the total number of tests executed.
+.TP
+skips
+Tests that were skipped, usually because of missing dependencies. These
+are marked "SKIPPED" in the normal test output.
+.TP
+expectedFailures
+Tests that failed, but were expected to fail, usually because the test
+is for a feature that hasn't been implemented yet. These are marked
+"TODO" in the normal test output.
+.TP
+unexpectedSuccesses
+Tests that should have been listed under expectedFailures, except that
+for some reason the test succeeded. These are marked "SUCCESS!?!" in
+the normal test output.
+.SH OPTIONS
+.TP
+\fB-b\fR, \fB--debug\fR
+Run the tests in the Python debugger. Also does post-mortem
+debugging on exceptions. Will load `.pdbrc' from current directory if
+it exists.
+.TP
+\fB-B\fR, \fB--debug-stacktraces\fR
+Report Deferred creation and callback stack traces
+.TP
+\fB--coverage\fR
+Generate coverage information in the `coverage' subdirectory of the trial temp
+directory (`_trial_temp' by default). For each Python module touched by the
+execution of the given tests, a file will be created in the coverage directory
+named for the module's fully-qualified name with the suffix `.cover'. For
+example, because the trial test runner is written in Python, the coverage
+directory will almost always contain a file named `twisted.trial.runner.cover'.
+
+Each `.cover' file contains a copy of the Python source of the module in
+question, with a prefix at the beginning of each line containing coverage
+information. For lines that are not executable (blank lines, comments, etc.)
+the prefix is blank. For executable lines that were run in the course of the
+test suite, the prefix is a number indicating the number of times that line was
+executed. The string `>>>>>>' prefixes executable lines that were not executed
+in the course of the test suite.
+
+Note that this functionality uses Python's sys.settrace() function, so tests
+that call sys.settrace() themselves are likely to break trial's coverage
+functionality.
+.TP
+\fB--disablegc\fR
+Disable the garbage collector for the duration of the test run. As each test is
+run, trial saves the TestResult objects, which means that Python's garbage
+collector has more non-garbage objects to wade through, making each
+garbage-collection run slightly slower. Disabling garbage collection entirely
+will make some test suites complete faster (contrast --force-gc, below), at the
+cost of increasing (possibly greatly) memory consumption. This option also makes
+tests slightly more deterministic, which might help debugging in extreme
+circumstances.
+.TP
+\fB-e\fR, \fB--rterrors\fR
+Print tracebacks to standard output as soon as they occur
+.TP
+\fB--force-gc\fR
+Run gc.collect() before and after each test case. This can be used to
+isolate errors that occur when objects get collected. This option would be
+the default, except it makes tests run about ten times slower.
+.TP
+\fB-h\fR, \fB--help\fR
+Print a usage message to standard output, then exit.
+.TP
+\fB--help-reporters\fR
+Print a list of valid reporters to standard output, then exit. Reporters can
+be selected with the --reporter option described below.
+.TP
+\fB--help-reactors\fR
+Print a list of possible reactors to standard output, then exit. Not all listed
+reactors are available on every platform. Reactors can be selected with the
+--reactor option described below.
+.TP
+\fB-l\fR, \fB--logfile\fR \fIlogfile\fR
+Direct the log to a different file. The default file is `test.log'.
+\fIlogfile\fR is relative to _trial_temp.
+.TP
+\fB-n\fR, \fB--dry-run\fR
+Go through all the tests and make them pass without running.
+.TP
+\fB-N\fR, \fB--no-recurse\fR
+By default, trial recurses through packages to find every module inside
+every subpackage. Unless, that is, you specify this option.
+.TP
+\fB--nopm\fR
+Don't automatically jump into debugger for post-mortem analysis of
+exceptions. Only usable in conjunction with --debug.
+.TP
+\fB--profile\fR
+Run tests under the Python profiler.
+.TP
+\fB-r\fR, \fB--reactor\fR \fIreactor\fR
+Choose which reactor to use. See --help-reactors for a list.
+.TP
+\fB--recursionlimit\fR
+Set Python's recursion limit. See sys.setrecursionlimit()
+.TP
+\fB--reporter\fR
+Select the reporter to use for trial's output. Use the --help-reporters
+option to see a list of valid reporters.
+.TP
+\fB--spew\fR
+Print an insanely verbose log of everything that happens. Useful when
+debugging freezes or locks in complex code.
+.TP
+\fB--tbformat\fR \fIformat\fR
+Format to display tracebacks with. Acceptable values are `default', `brief'
+and `verbose'. `brief' produces tracebacks that play nicely with Emacs' GUD.
+.TP
+\fB--temp-directory\fR \fIdirectory\fR
+WARNING: Do not use this options unless you know what you are doing.
+By default, trial creates a directory called _trial_temp under the current
+working directory. When trial runs, it first \fIdeletes\fR this directory,
+then creates it, then changes into the directory to run the tests. The log
+file and any coverage files are stored here. Use this option if you wish to
+have trial run in a directory other than _trial_temp. Be warned, trial
+will \fIdelete\fR the directory before re-creating it.
+.TP
+\fB--testmodule\fR \fIfilename\fR
+Ask trial to look into \fIfilename\fR and run any tests specified using the
+Emacs-style buffer variable `test-case-name'.
+.TP
+\fB--unclean-warnings\fR
+As of Twisted 8.0, trial will report an error if the reactor is left unclean
+at the end of the test. This option is provided to assist in migrating from
+Twisted 2.5 to Twisted 8.0 and later. Enabling this option will turn the errors
+into warnings.
+.TP
+\fB-u\fR, \fB--until-failure\fR
+Keep looping the tests until one of them raises an error or a failure.
+This is particularly useful for reproducing intermittent failures.
+.TP
+\fB--version\fR
+Prints the Twisted version number and exit.
+.TP
+\fB--without-module\fR \fImodulenames\fR
+Simulate the lack of the specified comma-separated list of modules. This makes
+it look like the modules are not present in the system, causing tests to check
+the behavior for that configuration.
+.TP
+\fB-z\fR, \fB--random\fR [\fIseed\fR]
+Run the tests in random order using the specified seed.
+.PP
+.SH SEE ALSO
+The latest version of the trial documentation can be found at
+http://twistedmatrix.com/documents/current/core/howto/testing.html
+.SH AUTHOR
+Written by Jonathan M. Lange
+.SH "REPORTING BUGS"
+To report a bug, visit http://twistedmatrix.com/trac/newticket
+.SH COPYRIGHT
+Copyright \(co 2003-2011 Twisted Matrix Laboratories
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/man/twistd-man.html b/doc/core/man/twistd-man.html
new file mode 100644
index 0000000..7761034
--- /dev/null
+++ b/doc/core/man/twistd-man.html
@@ -0,0 +1,187 @@
+
+
+Twisted Documentation: TWISTD.1
+
+
+
+
+ TWISTD.1
+
+
+
+
+
+
NAME
+
+
twistd - run Twisted applications (TACs, TAPs)
+
+
+
SYNOPSIS
+
+
twistd [options]
+
+
+
DESCRIPTION
+
+
Read a twisted.application.service.Application out of a file and run it.
+
+
+
OPTIONS
+
+
-n , --nodaemon
+Don't daemonize (stay in foreground).
+
-q , --quiet
+No-op for backwards compatibility.
+
+
+-p , --profile <profile output>
+Run the application under the profiler, dumping results to the specified file.
+
+
+--profiler <profiler name>
+Specify the profiler to use. Defaults to the 'hotshot' profiler.
+
+
+--savestats
+Save the Stats object rather than the text output of the profiler.
+
+
+-b , --debug
+Run the application in the Python Debugger (implies --nodaemon option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+
+
+-e , --encrypted <file>
+The specified tap/aos file is encrypted.
+
+
+--euid
+Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+
+
+-o , --no_save
+Do not save shutdown state.
+
+
+--originalname
+Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+
+
+-l , --logfile <logfile>
+Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+
+
+-l , --logger <fully qualified python name>
+A fully-qualified name to a log observer factory to use for the initial log
+observer. Takes precedence over --logfile and --syslog.
+
+
+--pidfile <pidfile>
+Save pid in specified file (default: twistd.pid).
+
+
+--chroot <directory>
+Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+
+
+-d , --rundir <directory>
+Change to a supplied directory before running (default: .).
+
+
+-u , --uid <uid>
+The uid to run as (default: don't change).
+
+
+-g , --gid <gid>
+The gid to run as (default: don't change).
+
+
+--umask <mask>
+The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+
+
+-r , --reactor <reactor>
+Choose which reactor to use. See --help-reactors for a list of
+possibilities.
+
+
+--help-reactors
+List the names of possibly available reactors.
+
+
+--spew
+Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+
+
+-f , --file <tap file>
+Read the given .tap file (default: twistd.tap).
+
+
+-s , --source <tas file>
+Load an Application from the given .tas (AOT Python source) file.
+
+
+-y , --python <python file>
+Use the variable application from the given Python file. This option overrides
+-f . This option implies --no_save .
+
+
+--syslog
+Log to syslog instead of a file.
+
+
+--version
+Print version information and exit.
+
+
+--prefix <prefix>
+Use the specified prefix when logging to logfile. Default is twisted .
+
+
+
+
+
+
+
Note that if twistd is run as root, the working directory is not
+searched for Python modules.
+
+
+
SIGNALS
+
+
A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+
+
+
AUTHOR
+
+
Written by Moshe Zadka, based on twistd's help messages.
+
+
+
REPORTING BUGS
+
+
To report a bug, visit
+http://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess
+
+
+
COPYRIGHT
+
+
Copyright © 2001-2011 Twisted Matrix Laboratories.
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/man/twistd.1 b/doc/core/man/twistd.1
new file mode 100644
index 0000000..c5c4c3c
--- /dev/null
+++ b/doc/core/man/twistd.1
@@ -0,0 +1,118 @@
+.TH TWISTD "1" "Dec 2011" "" ""
+.SH NAME
+twistd \- run Twisted applications (TACs, TAPs)
+.SH SYNOPSIS
+.B twistd
+[options]
+.SH DESCRIPTION
+Read a twisted.application.service.Application out of a file and run it.
+.SH OPTIONS
+\fB\-n\fR, \fB\--nodaemon\fR
+Don't daemonize (stay in foreground).
+.TP
+\fB\-q\fR, \fB\--quiet\fR
+No-op for backwards compatibility.
+.TP
+\fB\-p\fR, \fB\--profile\fR \fI\fR
+Run the application under the profiler, dumping results to the specified file.
+.TP
+\fB\--profiler\fR \fI\fR
+Specify the profiler to use. Defaults to the 'hotshot' profiler.
+.TP
+\fB--savestats\fR
+Save the Stats object rather than the text output of the profiler.
+.TP
+\fB\-b\fR, \fB\--debug\fR
+Run the application in the Python Debugger (implies \fB\--nodaemon\fR option).
+Sending a SIGINT or SIGUSR2 signal to the process will drop it into the
+debugger.
+.TP
+\fB\-e\fR, \fB\--encrypted\fR \fI\fR
+The specified tap/aos file is encrypted.
+.TP
+\fB--euid\fR
+Set only effective user-id rather than real user-id. This option has no
+effect unless the server is running as root, in which case it means not
+to shed all privileges after binding ports, retaining the option to regain
+privileges in cases such as spawning processes. Use with caution.
+.TP
+\fB\-o\fR, \fB\--no_save\fR
+Do not save shutdown state.
+.TP
+\fB\--originalname\fR
+Behave as though the specified Application has no process name set, and run
+with the standard process name (the Python binary in most cases).
+.TP
+\fB\-l\fR, \fB\--logfile\fR \fI\fR
+Log to a specified file, - for stdout (default: twistd.log).
+The log file will be rotated on SIGUSR1.
+.TP
+\fB\-l\fR, \fB\--logger\fR \fI\fR
+A fully-qualified name to a log observer factory to use for the initial log
+observer. Takes precedence over --logfile and --syslog.
+.TP
+\fB\--pidfile\fR \fI\fR
+Save pid in specified file (default: twistd.pid).
+.TP
+\fB\--chroot\fR \fI\fR
+Chroot to a supplied directory before running (default: don't chroot).
+Chrooting is done before changing the current directory.
+.TP
+\fB\-d\fR, \fB\--rundir\fR \fI\fR
+Change to a supplied directory before running (default: .).
+.TP
+\fB\-u\fR, \fB\--uid\fR \fI\fR
+The uid to run as (default: don't change).
+.TP
+\fB\-g\fR, \fB\--gid\fR \fI\fR
+The gid to run as (default: don't change).
+.TP
+\fB--umask\fR \fI\fR
+The (octal) file creation mask to apply. (default: 0077 for daemons, no
+change otherwise).
+.TP
+\fB\-r\fR, \fB\--reactor\fR \fI\fR
+Choose which reactor to use. See \fB\--help-reactors\fR for a list of
+possibilities.
+.TP
+\fB--help-reactors\fR
+List the names of possibly available reactors.
+.TP
+\fB\--spew\fR
+Write an extremely verbose log of everything that happens. Useful for
+debugging freezes or locks in complex code.
+.TP
+\fB\-f\fR, \fB\--file\fR \fI\fR
+Read the given .tap file (default: twistd.tap).
+.TP
+\fB\-s\fR, \fB\--source\fR \fI\fR
+Load an Application from the given .tas (AOT Python source) file.
+.TP
+\fB\-y\fR, \fB\--python\fR \fI\fR
+Use the variable "application" from the given Python file. This option overrides
+\fB\-f\fR. This option implies \fB\--no_save\fR.
+.TP
+\fB\--syslog\fR
+Log to syslog instead of a file.
+.TP
+\fB\--version\fR
+Print version information and exit.
+.TP
+\fB\--prefix\fR \fI\fR
+Use the specified prefix when logging to logfile. Default is "twisted".
+.PP
+Note that if \fBtwistd\fR is run as root, the working directory is \fInot\fR
+searched for Python modules.
+.SH SIGNALS
+A running twistd accepts SIGINT for a clean shutdown and SIGUSR1 to rotate log
+files.
+.SH AUTHOR
+Written by Moshe Zadka, based on twistd's help messages.
+.SH "REPORTING BUGS"
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#DevelopmentProcess\fR
+.SH COPYRIGHT
+Copyright \(co 2001-2011 Twisted Matrix Laboratories.
+.br
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/doc/core/specifications/banana.html b/doc/core/specifications/banana.html
new file mode 100644
index 0000000..53405de
--- /dev/null
+++ b/doc/core/specifications/banana.html
@@ -0,0 +1,199 @@
+
+
+Twisted Documentation: Banana Protocol Specifications
+
+
+
+
+ Banana Protocol Specifications
+
+
+
+
+
Introduction
+
+
+ Banana is an efficient, extendable protocol for sending and receiving s-expressions.
+ A s-expression in this context is a list composed of byte strings, integers,
+ large integers, floats and/or s-expressions.
+
+
+
Banana Encodings
+
+
+ The banana protocol is a stream of data composed of elements. Each element has the
+ following general structure - first, the length of element encoded in base-128, least signficant
+ bit first. For example length 4674 will be sent as 0x42 0x24
. For certain element
+ types the length will be omitted (e.g. float) or have a different meaning (it is the actual
+ value of integer elements).
+
+
+
+ Following the length is a delimiter byte, which tells us what kind of element this
+ is. Depending on the element type, there will then follow the number of bytes specified
+ in the length. The byte's high-bit will always be set, so that we can differentiate
+ between it and the length (since the length bytes use 128-base, their high bit will
+ never be set).
+
+
+
Element Types
+
+
+ Given a series of bytes that gave us length N, these are the different delimiter bytes:
+
+
+
+ List -- 0x80
+
+ The following bytes are a list of N elements. Lists may be nested,
+ and a child list counts as only one element to its parent (regardless
+ of how many elements the child list contains).
+
+ Integer -- 0x81
+ The value of this element is the positive integer N. Following bytes are not part of this element. Integers can have values of 0 <= N <= 2147483647.
+
+ String -- 0x82
+ The following N bytes are a string element.
+
+ Negative Integer -- 0x83
+ The value of this element is the integer N * -1, i.e. -N. Following bytes are not part of this element. Negative integers can have values of 0 >= -N >= -2147483648.
+
+ Float - 0x84
+ The next 8 bytes are the float encoded in IEEE 754 floating-point double format bit layout.
+ No length bytes should have been defined.
+
+
+ Large Integer -- 0x85
+ The value of this element is the positive large integer N. Following bytes are not part of this element. Large integers have no size limitation.
+
+ Large Negative Integer -- 0x86
+ The value of this element is the negative large integer -N. Following bytes are not part of this element. Large integers have no size limitation.
+
+
+
+ Large integers are intended for arbitary length integers. Regular integers types (positive and negative) are limited to 32-bit values.
+
+
+
Examples
+
+
+ Here are some examples of elements and their encodings - the type bytes are marked in bold:
+
+
+
+ 1
+ 0x01 0x81
+ -1
+ 0x01 0x83
+ 1.5
+ 0x84 0x3f 0xf8 0x00 0x00 0x00 0x00 0x00 0x00
+ "hello"
+ 0x05 0x82 0x68 0x65 0x6c 0x6c 0x6f
+ []
+ 0x00 0x80
+ [1, 23]
+ 0x02 0x80 0x01 0x81 0x17 0x81
+ 123456789123456789
+ 0x15 0x3e 0x41 0x66 0x3a 0x69 0x26 0x5b 0x01 0x85
+ [1, ["hello"]]
+ 0x02 0x80 0x01 0x81 0x01 0x80 0x05 0x82 0x68 0x65 0x6c 0x6c 0x6f
+
+
+
Profiles
+
+
+ The Banana protocol is extendable. Therefore, it supports the concept of profiles. Profiles allow
+ developers to extend the banana protocol, adding new element types, while still keeping backwards
+ compatability with implementations that don't support the extensions. The profile used in each
+ session is determined at the handshake stage (see below.)
+
+
+
+ A profile is specified by a unique string. This specification defines two profiles
+ - "none"
and "pb"
. The "none"
profile is the standard
+ profile that should be supported by all Banana implementations.
+ Additional profiles may be added in the future.
+
+
+
The "none"
Profile
+
+
+ The "none"
profile is identical to the delimiter types listed above. It is highly recommended
+ that all Banana clients and servers support the "none"
profile.
+
+
+
The "pb"
Profile
+
+
+ The "pb"
profile is intended for use with the Perspective Broker protocol, that runs on top
+ of Banana. Basically, it converts commonly used PB strings into shorter versions, thus
+ minimizing bandwidth usage. It starts with a single byte, which tells us to which string element
+ to convert it, and ends with the delimiter byte, 0x87
, which should not be prefixed
+ by a length.
+
+
+
+ 0x01 'None'
+ 0x02 'class'
+ 0x03 'dereference'
+ 0x04 'reference'
+ 0x05 'dictionary'
+ 0x06 'function'
+ 0x07 'instance'
+ 0x08 'list'
+ 0x09 'module'
+ 0x0a 'persistent'
+ 0x0b 'tuple'
+ 0x0c 'unpersistable'
+ 0x0d 'copy'
+ 0x0e 'cache'
+ 0x0f 'cached'
+ 0x10 'remote'
+ 0x11 'local'
+ 0x12 'lcache'
+ 0x13 'version'
+ 0x14 'login'
+ 0x15 'password'
+ 0x16 'challenge'
+ 0x17 'logged_in'
+ 0x18 'not_logged_in'
+ 0x19 'cachemessage'
+ 0x1a 'message'
+ 0x1b 'answer'
+ 0x1c 'error'
+ 0x1d 'decref'
+ 0x1e 'decache'
+ 0x1f 'uncache'
+
+
+
Protocol Handshake and Behaviour
+
+
+ The initiating side of the connection will be referred to as client , and the other
+ side as server .
+
+
+
+ Upon connection, the server will send the client a list of string elements, signifying
+ the profiles it supports. It is recommended that "none"
be included in this list. The client
+ then sends the server a string from this list, telling the server which profile it wants to
+ use. At this point the whole session will use this profile.
+
+
+
+ Once a profile has been established, the two sides may start exchanging elements. There is no
+ limitation on order or dependencies of messages. Any such limitation (e.g. server can only
+ send an element to client in response to a request from client ) is application specific.
+
+
+
+ Upon receiving illegal messages, failed handshakes, etc., a Banana client or server should
+ close its connection.
+
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/core/specifications/index.html b/doc/core/specifications/index.html
new file mode 100644
index 0000000..ac19236
--- /dev/null
+++ b/doc/core/specifications/index.html
@@ -0,0 +1,21 @@
+
+
+Twisted Documentation: Specifications
+
+
+
+
+ Specifications
+
+
+
+ Index
+ Version: 12.1.0
+
+
\ No newline at end of file
diff --git a/doc/fun/Twisted.Quotes b/doc/fun/Twisted.Quotes
new file mode 100644
index 0000000..e69de29
diff --git a/doc/fun/lightbulb b/doc/fun/lightbulb
new file mode 100644
index 0000000..12de989
--- /dev/null
+++ b/doc/fun/lightbulb
@@ -0,0 +1,7 @@
+Q. How many Twisted developers does it take to screw in a lightbulb?
+
+A. Three to implement twisted.lightbulb, one to refactor it, four to whine until the API is documented, two to re-implement it as a C module, one to package it up nicely, but nobody uses the packaged lightbulb, because they need the newest light features, and then no one actually gets around to screwing it in.
+%
+Q. How many Divmod developers does it take to reboot a server?
+A. Four. One to drive, one to recompile the kernel and one to report the progress on IRC.
+%
diff --git a/doc/fun/register.html b/doc/fun/register.html
new file mode 100644
index 0000000..3fe220b
--- /dev/null
+++ b/doc/fun/register.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+ Twisted Matrix Labs Software Registration
+
+
+
+ How to Register your Twisted Daemon instance
+
+ The Problem
+
+ The Business Software Alliance may have put it best, in
+ their page on anti-piracy :
+
+
+ "Software is one of the most valuable technologies of the
+ Information Age, running everything from PCs to the Internet.
+ Unfortunately, because software is so valuable, and because
+ computers make it easy to create an exact copy of a program
+ in seconds, software piracy is widespread. From individual
+ computer users to professionals who deal wholesale in stolen
+ software, piracy exists in homes, schools, businesses and
+ government. Software pirates not only steal from the
+ companies that make the software, but with less money for
+ research and development of new software, all users are hurt.
+ That's why all software piracy - even one copy you make for a
+ friend, is illegal."
+
+
+ Software piracy is a serious crime, and one that the Open
+ Source community has been remarkably lax in pursuing and
+ protecting against. This is why Twisted Matrix Laboratories is
+ taking the forefront in Open Source software registration
+ technology.
+
+ The Twisted Solution
+
+ In order to do your part to prevent the tragedy of
+ unregistered, unlicensed software, all you need to do is visit
+ the Twisted
+ Matrix Labs Licensing and Registration page , and enter your
+ user information to obtain a license key. You can provide us
+ with as much or as little information as you like!
+
+ This will produce a plain-text file which you should save as
+ "twisted-registration" (no quotes, no extension) and drop into
+ the same folder where you run your twistd
server.
+ It will be automatically recognized by the server upon
+ startup.
+
+ Other Benefits to You, the User
+
+ Besides providing you with a license for the use of your
+ Twisted Daemon, a Twisted registration file also provides you
+ with a probably-unique identifier for your Twisted process -- a
+ helpful tool in writing peer-to-peer applications, or assigning
+ distributed database keys. Privacy is important to us, and
+ this ID is never used by Twisted Matrix Labs software
+ for tracking purposes. You need only register once, rather
+ than renegotiating unique IDs upon every run of your peered
+ application.
+
+ Registering with a valid e-mail address also gives Twisted
+ Matrix Labs a way to contact you with information about
+ upgrades and services as they become available. (Again,
+ registering with an e-mail address is strictly
+ optional to obtain your license key. If you don't want to
+ receive this information, you don't have to!)
+
+ Thank you for doing your part to end the piracy crisis.
+
+
+
diff --git a/doc/historic/2002/ipc10/twisted-network-framework/errata.html b/doc/historic/2002/ipc10/twisted-network-framework/errata.html
new file mode 100644
index 0000000..8388919
--- /dev/null
+++ b/doc/historic/2002/ipc10/twisted-network-framework/errata.html
@@ -0,0 +1,256 @@
+
+
+
+
+
+ The World of Software is a World of Constant
+ Change
+
+
+
+ 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.
+
+ The World of Software is a World of Constant Change
+
+ 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.
+
+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()
+
+
+
+
+
diff --git a/doc/historic/2002/ipc10/twisted-network-framework/index.html b/doc/historic/2002/ipc10/twisted-network-framework/index.html
new file mode 100644
index 0000000..88fb488
--- /dev/null
+++ b/doc/historic/2002/ipc10/twisted-network-framework/index.html
@@ -0,0 +1,1568 @@
+
+
+
+
+
+ The Twisted Network Framework
+
+
+
+ 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.
+
+ The Twisted Network Framework
+
+
+
+
+
+ Abstract
+
+ 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
+
+ Introduction
+
+ 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:
+
+
+ Handle each connection in a separate process
+
+ Handle each connection in a separate thread
+
+ Use non-blocking system calls to handle all connections
+ in one thread.
+
+
+ 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.
+
+ Complementing Python
+
+ 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.
+
+ Configuration
+
+ 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.
+
+ Ports, Protocol and Protocol Factories
+
+ 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
+
+
+ The Event Loop
+
+ 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.
+
+ Going Higher Level
+
+ 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 not self.factory.domains.has_key(domain):
+ 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.
+
+ The 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
+
+
+ Multi-thread Support
+
+ 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.
+
+ Twisted Mail Server
+
+ 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.
+
+ Introducing Perspective Broker
+
+ All the World's a Game
+
+ 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.
+
+ Translucent, not Transparent
+
+ 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
+
+
+ Different Behavior for Different Perspectives
+
+ 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
.
+
+
+
+# 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
+
+ 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 view_xxx
methods.
+
+ Mechanisms for Sharing State
+
+ 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.
+
+
+ Publishing Objects with PB
+
+ 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.
+
+
+ A SimpleService is created and persistently added to the
+ Application
instance.
+
+ A SimplePerspective is created, via the overridden
+ getPerspectiveNamed
method.
+
+ That 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
+
+
+ Event-Driven Web Object Publishing with Web.Widgets
+
+ 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.
+
+ Things That Twisted Does Not Do
+
+ 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.
+
+ Future Directions
+
+ 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.
+
+ Conclusions
+
+ 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.
+
+ Acknowledgements
+
+ 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.
+
+ References
+
+
+ The Twisted site, http://www.twistedmatrix.com
+
+ Douglas Schmidt, Michael Stal, Hans Rohnert and Frank
+ Buschmann, Pattern-Oriented Software Architecture, Volume 2,
+ Patterns for Concurrent and Networked Objects, John Wiley
+ & Sons
+
+ Abhishek Chandra, David Mosberger, Scalability of Linux
+ Event-Dispatch Mechanisms, USENIX 2001,
+ http://lass.cs.umass.edu/~abhishek/papers/usenix01/paper.ps
+
+ Protocol specifications, http://www.rfc-editor.com
+
+ The Twisted Philosophical FAQ,
+ http://www.twistedmatrix.com/page.epy/twistedphil.html
+
+ Twisted Advocacy,
+ http://www.twistedmatrix.com/page.epy/whytwisted.html
+
+ Medusa, http://www.nightmare.com/medusa/index.html
+
+ Using Spreadable Web Servers,
+ http://www.twistedmatrix.com/users/jh.twistd/python/moin.cgi/TwistedWeb
+
+ Twisted Spread implementations for other languages,
+ http://www.twistedmatrix.com/users/washort/
+
+ PHP: Hypertext Preprocessor, http://www.php.net/
+
+ The Z Object Publishing Environment,
+ http://www.zope.org/, http://zope.com/
+
+
+
+
diff --git a/doc/historic/2003/europython/doanddont.html b/doc/historic/2003/europython/doanddont.html
new file mode 100644
index 0000000..79b0072
--- /dev/null
+++ b/doc/historic/2003/europython/doanddont.html
@@ -0,0 +1,508 @@
+Idioms and Anti-Idioms in Python
+
+Idioms and Anti-Idioms in Python
+
+Idioms and Anti-Idioms, AKA Do and Don't
+Welcome
+
+Gimmick -- Charmed quotes
+
+
+
+Prue (Something Wicca This Way Comes, season 1) -- No, we are not supposed to use our powers
+Python
+Few gotchas...
+
+...but not zero
+
+Most are easy to avoid...
+
+...if you know about them.
+
+
+
+Prue (Something Wicca This Way Comes, season 1) -- Uh, it doesn't work out there either.
+Exceptions
+Primary method of dealing with errors
+
+Flexible
+
+Good opportunity to shoot yourself in foot...
+
+...without knowing about it (bug only shows up rarely.)
+
+
+
+Leo (Paige From the Past, season 4) -- You have to [...] Paige. No exceptions.
+Exceptions -- Catching Too Much
+Classical case: 'except:'
+
+Will catch anything
+
+Including most bugs...NameError, AttributeError...
+
+
+
+
+Piper (Charmed Again, season 4) -- Okay, well this is way too much for me to handle.
+Exceptions -- Catching Too Much -- Example
+
+try:
+ f = opne("file")
+except:
+ sys.exit("no such file")
+
+
+
+Piper (Charmed Again, season 4) -- Way too much.
+Exceptions -- Catching Too Soon
+The slogan:Don't catch errors you can do nothing about
+
+
+Catching exceptions should by the 'user'
+
+The point where the value is *used*
+
+
+
+Phoebe (Knight to Remember, season 4) -- Maybe it's just too soon.
+Exceptions -- Catching Too Soon -- Example
+
+def readlinesfromfile(file):
+ try:
+ return open(file).readlines()
+ except IOError:
+ pass # do what?
+
+What can we do?Return empty list? bad
+
+Print warning? what if it's one of several possibilities
+
+Exit? NO!
+
+Raise our own exception? Losing information
+
+
+
+
+Paige (Knight to Remember, season 4) -- I'm already a little late.
+Catching multiple exceptions
+
+
+try:
+ fp = open("file")
+except IOError, OSError:
+ print "could not open file"
+
+
+Paige (Knight to Remember, season 4) -- Because I've got too many responsibilities
+Catching multiple exceptions (cont'd)
+Bug:
+
+Only IOError gets caught...
+
+and exception value is put in OSError
+
+But most exceptions are IOError :(
+
+Likely to not discover this bug
+
+
+
+Piper (Knight to Remember, season 4) -- Alright! Calm down!
+Catching multiple exceptions (cont'd 2)
+
+
+try:
+ fp = open("file")
+except (IOError, OSError):
+ print "could not open file"
+
+
+Phoebe (Knight to Remember, season 4) -- Besides that, maybe we can help
+Catching NameError
+
+
+try:
+ import foo
+except ImportError:
+ pass
+
+try:
+ foo.Function()
+except NameError:
+ pass # some replacement
+
+
+
+Piper (Morality Bites, season 4) -- That's OK, I forgot your name too.
+Catching NameError (cont'd)
+
+
+try:
+ import foo
+except ImportError:
+ foo = None
+
+if foo is not None:
+ foo.Function()
+else:
+ pass # some replacement
+
+If foo.Function() sometimes has a NameError, we won't mask it...
+
+...or if we misspell 'foo'
+
+
+
+Anne (Morality Bites, season 4) -- Oh, right, sorry.
+Importing Modules -- A Review
+'import module'
+
+'from module import name1, name2'
+
+Only imports once (or does it?)
+
+
+
+Phoebe (Animal Pragmatism, season 2) -- Rome was not built in a day,
+Importing __main__
+__main__ is where the 'script' is executed
+
+Avoid the temptation to import __main__
+
+Put common function in a named module
+
+Then your code will be more useful
+
+
+
+Piper (Animal Pragmatism, season 2) -- And why mess with a good thing?
+Importing a File Twice
+But it can't be, can it?
+
+Importing a script into itself
+
+
+# file: hello.py
+import hello
+class Foo: pass
+
+Two 'Foo's, same definition, different class!
+
+If your sys.path includes packages...
+
+...you can import a module once from a package and once plain
+
+
+
+Phoebe (Which Prue Is It, Anyway?, season 1) -- Okay, which one of you is the real Prue?
+Importing *
+Don't do it
+
+Classic mistake:
+
+
+from os import *
+
+fp = open("file") # works
+fp.readline() # fails with a weird error...?
+
+os.open returns a file descriptor (number)
+
+
+
+Pink Prue (Which Prue Is It, Anyway?, season 1) -- So, um, what did I do now?
+Importing * Inside Functions
+Just invalid Python...
+
+...but happens to work in 1.5.2...
+
+...and sometimes in 2.1...
+
+...never in 2.2.
+
+Just Say No
+
+
+
+Pink Prue (Which Prue Is It, Anyway?, season 1) -- What ever it is, I have an alibi.
+Importing Names
+from foo import name1, name2
+
+Not a bad idea always
+
+But be careful of repercussions:
+
+modules sometimes change things inside
+
+You won't see those changes
+
+Opportunity for inconsistency!
+
+
+
+Real Prue (Which Prue Is It, Anyway?, season 1) -- Because I still have to work here when all of this is over.
+Reloading
+reload(module) -- reread module from file
+
+Useful in long running processes?
+
+Doesn't play nice with 'from import name'
+
+Beware of exceptions: the new classes are different from old classes
+
+
+
+Real Prue (Which Prue Is It, Anyway?, season 1) -- Don't worry I'm never casting that spell again.
+exec, execfile and eval
+Execute arbitrary Python code
+
+No-cost scripting language for applications
+
+But easy to shoot one's self in the foot
+
+
+
+Reporter (Morality Bites, season 2) -- More news on the execution of Phoebe Halliwell coming up.
+exec, execfile and eval -- Modify namespaces
+They modify the namespace they're in
+
+Depends on global vs. inside functions
+
+Use with care -- or with explicit dictionaries
+
+
+
+Nathaniel (Morality Bites, season 2) -- Executions are a bitch to plan.
+exec, execfile and eval -- Inside functions
+Unadorned exec is invalid inside functions
+
+execfile and eval play badly with local var. optimisation
+
+Always use with explicit dictionary
+
+
+
+Nathaniel (Morality Bites, season 2) -- Phoebe, what is this? An attempt to stay your execution?
+Conclusion: recommended usage
+d={};exec "code" in d
+
+d={};execfile("file", d)
+
+d={};eval("expression", d)
+
+Sometimes useful to pre-populate dictionary
+
+
+
+Phoebe (Morality Bites, season 2) -- Just because you don't understand something, doesn't make it evil.
+exec, execfile and eval -- Restricted Execution (Don't)
+rexec never was audited
+
+History of holes
+
+Dangerous to allow arbitrary code
+
+DoS attacks not defended against at all
+
+Recursion
+
+
+
+Leo (Morality Bites, season 2) -- Nobody's gonna rescue you.
+Syntax
+Python syntax regular and nice...
+
+...but not perfect.
+
+Some care needed.
+
+
+
+Prue (Morality Bites, season 2) -- You know, we can still make the good things happen.
+Syntax -- Tabs and Spaces
+Use Tabs
+
+Or use spaces
+
+But don't mix them...
+
+...ever!
+
+Invites bugs
+
+
+
+Prue (The Painted World, season 2) -- We've seen so many bizarre things.
+Syntax -- Backslash Continuations
+
+
+# Extra newline
+r = 1 \
+
++2
+
+# Missing backslash in long series
+r = 1 \
++2 \
++3 \
++4
++5 \
++6
+
+Both *silently* do the wrong things
+
+Syntax -- Backslash Continuations (cont'd)
+
+
+
+
+# Extra newline
+r = (1
+
++2)
+
+# Long series
+r = (1
++2
++3
++4
++5
++6)
+
+
+
+Prue (The Painted World, season 2) -- Uh, what just happened here?
+Hand Hacking Batteries
+Don't write os.path functions yourselfos.path.join especially
+
+
+min, max
+
+urlparse
+
+Skim through modules list. A lot.
+
+
+
+Prue (Animal Pragmatism, season 2) -- Well, we didn't find anything in the Book Of Shadows.
+Further Reading
+
+Phoebe (The Painted World, season 2) -- I think you'll find me pretty knowledgeable about all areas
+Questions?
+Piper (The Painted World, season 2) -- You're like ask rainman.com
+
+Bonus Slides
+Phoebe (The Painted World, season 2) -- Oh, and P.S. there will be no personal gain.
+
+Packages and __init__.py
+Packages are determined by __init__.py files
+
+Temptation to put code in __init__.py
+
+But two namespaces mix: __init__'s and filesystem's
+
+Put comments, docstring and __all__
+
+
+
+Piper (Animal Pragmatism, season 2) -- It's a package. One I would like to share with you.
+Type Checking
+Python's typing is highly dynamic
+
+Capability-based, not class-based
+
+Explicit type checks hurt code usefulness
+
+(common use -- proxies, for testing)
+
+
+
+Phoebe (Black as Cole, season 2) -- I never thought of myself as the marrying type
+Type Checking -- Example
+
+
+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
+
+(inspired from a question on #python)
+
+More badness than you can shake a stick at.
+
+
+
+Phoebe (Muse to My Ears, season 4) -- You're an artistic, creative type.
+Type Checking -- Example -- Fixed
+
+
+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
+
+
+
+Phoebe (Muse to My Ears, season 4) -- You see how well this worked out?
+Private __Attributes
+Useful in deep hierarchies to keep attributes separate
+
+Mangle only class name -- *not* module name
+
+Makes it harder to test
+
+Makes it harder to hand-hack for debugging
+
+
+
+Tessa (Animal Pragmatism, season 2) -- Maybe it's our fault because we tried to make them into something they're not.
+
+Using Mutable Default Arguments
+
+
+def foo(l=[]):
+ l.append(5);return l
+
+
+
+Will modify the same list.
+If you want that -- use object, class, not that hack.
+
+
+
+def foo(l=None):
+ if l is None: l=[]
+ l.append(5);return l
+
+
+Snake guy (Animal Pragmatism, season 2) --
+You two are acting like nothing's changed.
+
+
diff --git a/doc/historic/2003/europython/index.html b/doc/historic/2003/europython/index.html
new file mode 100644
index 0000000..051fb6d
--- /dev/null
+++ b/doc/historic/2003/europython/index.html
@@ -0,0 +1,35 @@
+Moshe's Talks
+
+Moshe's talks
+
+Slides
+
+
+
+HTML
+
+
+
+PDF
+
+
+
+
diff --git a/doc/historic/2003/europython/lore.html b/doc/historic/2003/europython/lore.html
new file mode 100644
index 0000000..edb33cc
--- /dev/null
+++ b/doc/historic/2003/europython/lore.html
@@ -0,0 +1,502 @@
+Lore
+
+Lore
+Lore - A Document Generation System
+Gimmick -- Gilmore girls quotes
+
+Goal - take something which is easy to write, transforms to something easy to read
+
+For correct definitions of 'easy', of course
+
+
+
+Rory (Concert Interruptus, season 1) -- Yeah, well I've always thought easy is completely overrated.
+Source Format
+Subset of XHTML 1.0Except for some new attributes
+
+Shouldn't bother browsers
+
+
+Slanted towards logical markup
+
+
+
+Alex (I Solemnly Swear, season 3) -- That would've been far too logical.
+
+Output Formats
+Screen and paperScreen - 'fancy HTML'
+
+Paper - LaTeXUse LaTeX to produce PDF or PostScript
+
+
+
+
+
+Madelaine (The Lorelais' First Day at Chilton, season 1) -- You don't know she's going out for the paper.
+Minimal Lore Document
+
+<html>
+<head><title>Title</title></head>
+<body><h1>Title</h1></body>
+</html>
+
+
+Luke (There's the Rub, season 2) -- You said minimal
+Minimal Lore Document Explained
+title element in head -- a must
+
+h1 element in head -- a must
+
+
+
+Tom (There's the Rub, season 2) -- Hey, this is minimal
+External Listings
+Advantage -- no need to quote
+
+Advantage -- test your examples
+
+Example:<a class="python-listing" href="/usr/lib/python2.2/os.py">os.py</a>
+
+
+
+
+
+Kirk (Red Light on the Wedding Night, season 2) -- I include it as an example of the excellence I aspire to.
+Using Lore to Generate HTML
+Write template
+
+[optional] Write stylesheet
+
+Run lore
+
+
+
+Paris (Run Away, Little Boy, season 2) -- I went on the web and found this site
+Generating LaTeX
+lore -olatex file.html --> produces file.tex
+
+Default is to create an 'article'
+
+Creating PostScriptlatex file.tex
+
+latex file.tex
+
+dvips -o file.ps file.dvi
+
+
+Creating PDF latex file.tex
+pdflatex file.tex
+
+
+
+
+Rory (Christopher Returns, season 1) -- He had already printed like a million
+Using Lint
+lore -olint doc/howto/*.html
+
+lore -n -olint doc/howto/*.html #no output except warnings
+
+
+
+Max (The Deer-Hunters, season 1) -- I know a D seems pretty dismal
+Further Reading
+Man page -- doc/man/lore.xhtml
+
+Howto -- doc/howto/lore.xhtml
+
+Extending howto -- doc/howto/extending-lore.xhtml
+
+Documentation standard -- doc/howto/doc-standard.xhtml
+
+Lore paper -- doc/historic/2003/pycon/lore/lore.html
+
+
+
+Paris (The Bracebridge Dinner, season 2) -- Rereading the Iliad a third time is not not doing anything
+Questions?
+Lorelai (Forgiveness and Stuff, season 1) -- A person needs details.
+Bonus Slides
+Miss James (The Lorelais' First Day at Chilton, season 1) -- If you do it in Latin you get extra credit.
+Lore Alternatives - LaTeX
+Very good at printed results
+
+Model makes alternative parsers near-impossible
+
+Renderers to HTML are buggy and fragile
+
+People find it hard to use
+
+
+
+Michel (Love, Daisies and Troubadors, season 1) -- It increases my ennui
+Lore Alternatives - HTML
+Too flexible
+
+No support for needed idiomsSpecial-purpose Python markup
+
+Tables of contents
+
+Inlining
+
+Footnotes
+
+
+Renders badly to dead trees with current tools
+
+
+
+Lorelai (Love, Daisies and Troubadors, season 1) -- It was broken [...] I'm not crazy
+Lore Alternatives - Docbook
+Using correctly requires too much workWrite a DTD with special elements
+
+Write Jade stylesheets
+
+
+Lore is probably smaller than docbook specialisation
+
+People find it hard to use
+
+
+
+Rory (Hammers and Veils, season 2) -- What do you want me to do it?
+Lore Alternatives - Texinfo
+
+Man (Hammers and Veils, season 2) -- There's a ton of hurt that almost happened here.
+Lore Alternatives - reST
+Completely new language (no editor support)
+
+Hard to add new tags
+
+No linter
+
+
+
+Emily (Hammers and Veils, season 2) -- And this is what we need to discuss right now?
+Lore Alternatives - LyX
+
+Rory (Hammers and Veils, season 2) -- Well, it's just dressed up a little.
+Some Standard Tags -- XHTML Primer
+<p>paragraph</p>
+
+
+<em>emphasis</em>
+
+
+<strong>strong emphasis</strong>
+
+
+Headers<h2>sectionheader</h2> <h3>subsection</h3>
+
+Lists<ol><li>ordered list item</li></ol> <ul><li>unordered list item</li></ul>
+
+<img src="http://example.com/img.png" />
+
+
+
+
+Rory (Kiss and Tell, season 1) -- See, even a little information in your hands is dangerous.
+More HTML
+Indicating authorship -- <link rel="author" href="author@example.com" title="Author Name" />
+
+Put in <head>
+
+sub/sup -- subscripts, superscripts
+
+
+
+Max (The Lorelais' First Day at Chilton, season 1) -- Tolstoy's favourite author, for instance, was...
+More HTML -- cross references
+Label<a name="label-name" />
+
+
+Reference in file<a href="#label-name">reference text</a>
+
+
+Reference in other file<a href="file-name#label-name">reference text</a>
+
+
+Refer to URL<a href="http://example.com">reference text</a>
+
+
+
+
+Christopher (Christopher Returns, season 1) -- It's just a weird reference.
+Special Markup
+Things not in XHTML are done with div/span classes
+
+<div class="note">note</a> -- notes
+
+<div class="doit">doit</a> -- something not implemented
+
+<span class="footnote">footnote</a> -- put in a footnote
+
+
+
+Taylor (Take The Deviled Eggs, season 3) -- Out attention spans are gnat-like tonight
+API References
+<code class="API">urllib</code>
+
+
+<code base="urllib" class="API">urlencode</code>
+
+
+<code base="twisted" class="API">copyright.version</code>
+
+
+
+
+Lorelai (The Road Trip To Harvard, season 2) -- We're just kinda hanging out between classes
+API References Explained
+Integrate with systems for docstring generation
+
+Generate links to auto-generated docs
+
+
+
+Luke (Love and War and Snow, season 1) -- How do you know? Do you have written documentation?
+Inline Listings
+Use <pre>
+
+Possible classes: python, shell, python-interpreter
+
+Example:
+
+
+
+<pre class="python">
+def foo():
+ return forbnicate(4)
+</pre>
+
+Taylor (Take The Deviled Eggs, season 3) -- That's not even English.
+Inline Listings -- short
+Use <code>
+
+Possible classes: python, shell, py-signature
+
+...and more
+
+
+
+Rory (Double Date, season 1) -- It's like this weird code thing with her.
+Generating HTML -- writing templates
+Templates are XHTML documents
+
+Put in reference to stylesheet -- head is mostly kept as is
+
+Title will be prepended to document's title
+
+<div class="toc" /> will be replaced by table of contents
+
+<div class="body" /> will be replaced by processed body
+
+
+
+Paris (I Can't Get Started, season 2) -- How's this sound for a template?
+Generating HTML -- using commandline
+Full details: the lore manpage
+
+Basic format: lore file.html --> outputs file.xhtml--config template=template.tpl to use different template
+
+--config baseurl=format-string for the url of the auto-generated docstring docs
+
+
+
+
+Richard (The Third Lorelai, season 1) -- Your wish is my command.
+Generating HTML -- using commandline -- examples
+lore --config template=strange.tpl foo.html
+
+lore --docsdir doc/howto/
+
+lore -p --docsdir doc/howto/ # use plain progress
+
+
+
+Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried cake!
+Generating HTML -- using commandline -- examples (cont'd)
+lore --docsdir doc/howto/ --config baseurl=../api/%s.html
+
+lore --ext='' foo.html # produce 'foo' as output
+
+
+
+Jackson (A Deep-Fried Korean Thanksgiving, season 3) -- Deep-fried shoe!
+Generating HTML -- notes about stylesheets
+Many 'class's in the output
+
+The stylesheet Twisted uses can be used as exampleEspecially the .py-src-* classes: used for syntax highlighting
+
+
+
+
+Miss Patty (Cinnamon's Wake, season 1) -- If you had a better hair style I might consider dating
+Generating LaTeX -- examples
+lore -olatex --config section file.html
+
+lore -olatex --config book file.html
+
+lore -olatex --config section --docsdir doc/howto/
+
+
+
+Luke (Hammers and Veils, season 2) -- Just an example
+Using Lint -- notes
+If there is an element which lint gives a warning you disagree with: <element hlint="off">mistake</element>
+
+But usually the linter is right
+
+lint exits with non-zero status iff some document was not clean -- useful in shell scripts
+
+
+
+Paris (The Deer-Hunters, season 1) -- That would be cause for concern.
+Understanding Lint Warnings
+Format: file:line:column:warning
+
+Line/column always point to start/end of element
+
+Some justifications:<pre> with >80 characters/line renders badly, both in HTML and in LaTeX
+
+
+
+
+Jess (Teach Me Tonight, season 2) -- I appreciate the warning.
+Using Lore For Slides
+lore -ilore-slides -omgp file.html for magic point
+
+lore -ilore-slides -oprosper file.html for prosper
+
+lore -ilore-slides -ohtml file.html for HTML next/prev
+
+Splits on 'h2'
+
+Dogfooding
+
+
+
+Emily (Road Trip to Harvard, season 2) -- Why in the world do you insist on taking slides?
+Extending Lore
+Accept more input tags
+
+Change how documents are processed
+
+Add more output formats
+
+
+
+Rory (The Lorelais' First Day at Chilton, season 1) -- Well, add a couple of plaid skirts
+Extending Lore -- example
+We want to add a way to blink: <span class="blink">
+
+Modify HTML output
+
+Modify lint output
+
+Make it 'small caps' in LaTeX
+
+Distribute as package 'blinker'
+
+
+
+Lorelai (Presenting Lorelai Gilmore, season 2) -- No, no, if you wanna do it, I'll help. It's just weird.
+Extending Lore -- example (cont'd)
+
+# 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'))
+
+
+
+Christopher (Presenting Lorelai Gilmore, season 2) -- I can't believe you're letting her do it.
+Extending Lore -- example (cont'd 2)
+
+# blinker/latex.py
+class BlinkerLatexSpitter(latex.LatexSpitter):
+
+ def visitNode_span_blink(self, node):
+ self.writer('{\sc ')
+ self.visitNodeDefault(node)
+ self.writer('}')
+
+
+
+Lorelai (Presenting Lorelai Gilmore, season 2) -- I'm sorry, I meant what scenario on my planet
+Extending Lore -- example (cont'd 3)
+
+# 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!
+
+
+Rory (Presenting Lorelai Gilmore, season 2) -- Sorry, we haven't tamed my wild ways yet.
+Man page support
+No output
+
+Man->Lore conversion
+
+lore -iman -olint file.1 --> generates file.html
+
+
+
+Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?
+Man page support
+No output
+
+Man->Lore conversion
+
+lore -iman -olint file.1 --> generates file.html
+
+
+
+Lorelai (Concert Interruptus, season 1) -- would you like to write out some sort of instruction manual to go with the dishes?
+
+
diff --git a/doc/historic/2003/europython/slides-template.tpl b/doc/historic/2003/europython/slides-template.tpl
new file mode 100644
index 0000000..fd33fc3
--- /dev/null
+++ b/doc/historic/2003/europython/slides-template.tpl
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+ [ |
+ ]
+
+
+
+
+
+
diff --git a/doc/historic/2003/europython/tw-deploy.html b/doc/historic/2003/europython/tw-deploy.html
new file mode 100644
index 0000000..628d12a
--- /dev/null
+++ b/doc/historic/2003/europython/tw-deploy.html
@@ -0,0 +1,1106 @@
+A Twisted Web Tutorial
+
+A Twisted Web Tutorial
+
+Twisted Web -- The Tutorial
+Welcome
+
+Gimmick -- Buffy quotes
+
+
+
+Sweet (Once More With Feeling, season 6) -- Showtime
+Twisted Web
+Web server, using Twisted
+
+Serve static files
+
+Run CGIs
+
+Other uses...
+
+
+
+Giles (I Robot -- You Jane, season 1) -- There's a demon in the internet
+Short Example: Putting a Server Up
+Here's all you need to know to bring up a server
+
+
+% mktap --uid=33 --gid=33 web --path=/var/www/htdocs --port=80
+% sudo twistd -f web.tap
+
+The rest of the talk will explain what that means...
+
+...and how to do more complicated things
+
+
+
+Buffy (Once More, With Feeling, season 6) -- I've got a theory. It doesn't matter.
+Setup and Configuration Utilities
+mktap
+
+twistd
+
+websetrootWon't be covered, unless there is time
+
+
+
+
+Xander (The Harvest, season 1) -- crosses, garlic, stake through the heart
+Digression: What are TAPs
+Pickled 'application configuration'
+
+Object which contains all the information about application
+
+The canonical way to represent configurations in Twisted
+
+Machine editable
+
+
+
+Master (The Wish, season 3) -- Behold the technical wonder
+mktap
+General usage
+
+Flexibility and Power
+
+
+
+Buffy (Bad Eggs, season 2) -- I'm gonna need a *big* weapon
+mktap web: Common Useful Options
+--path: serve from given path
+
+--port: listen on given port
+
+--user: serve from users' directories and personal servers
+
+--logfile: log to NCSA compatible logfile given
+
+--processor: add a special processor for a given extension
+
+
+
+Buffy (Bad Eggs, season 2) -- That's probably not gonna be the winning argument, is it?
+
+twistd
+Start a Twisted Application
+
+Loads an instance of twisted.internet.app.Application from a file
+
+Daemonizes, binds to appropriate ports, and starts the Twisted mainloop
+
+
+
+Giles (Teacher's Pet, season 1) -- That's all he said? Fork Guy?
+What's a Resource?
+Everything represented as twisted.web.resource.Resource
+
+Important interface:
+
+
+
+Sean (Go Fish, season 3) -- You're soakin' in it, bud.
+Resource Examples
+Files
+
+OthersVirtual hosts
+
+User directories
+
+
+
+
+Xander (As You Were, season 6) -- We have friends, family and demons
+Web Development
+ProcessorsInherited from resource.Resource
+
+Interpret files as code rather than data
+
+
+Default processors.php -- default PHP
+
+.cgi -- Common Gateway Interface
+
+.rpy -- Correct way, Python scripting
+
+.trp -- Resource pickles
+...more
+
+
+You can also write your own
+
+
+
+Xander (Family, season 5) -- That was a tangled web
+Custom Processor
+A custom processor to handle Perl CGIs (in a module called PerlScript)
+
+
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+
+Use:mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript
+
+
+
+
+Tara (Family, season 4) -- There was the front of a camel
+Resource Scripting
+Subclass resource.Resource
+
+Write a render(self, request) methodReturn string for immediate response
+
+Return NOT_DONE_YET and write to request
+
+
+Create an .rpy file that sets 'resource' to an instance
+
+
+
+Tara (Once More, With Feeling, season 6) -- You make me complete
+.rpy example
+
+from twisted.web import resource as resourcelib
+
+class MyGreatResource(resourcelib.Resource):
+ def render(self, request):
+ return "<html>foo</html>"
+
+resource = MyGreatResource()
+
+
+Willow (Welcome to the Hellmouth, season 1) -- It's probably easy for you.
+Alternative Configuration Formats
+xml
+
+source
+
+Python
+
+
+
+Ben (The Gift, season 5) -- I wish there was another way
+Alternative Configuration Formats -- Python
+Write manually
+
+Just uses twisted.web API
+
+Possible to do anythingWrite loops
+
+Read other files
+
+(not recommended) Define functions or classes
+
+
+
+
+Buffy (The I In Team, season 4) -- But I've learned that it pays to be flexible in life.
+
+Python Configuration Example
+
+Create application
+
+Make it listen on port 80 for web requests...
+
+...which should be served from /var/www/htdocs
+
+
+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")))
+
+
+Willow (The Pack, season 1) -- It's simple, really.
+
+Bannerfish -- A Case Study in Deployment
+
+Xander (Halloween, season 2) -- Let's move out.
+Bannerfish -- Standalone tap
+mktap bannerfish
+
+twistd -f bannerfish.tap
+
+For full list of optionsmktap --help bannerfish
+
+
+
+
+Ethan (Halloween, season 2) -- Don't wish to blow my own trumpet, but --
+Bannerfish -- Standalone tap (behind reverse proxy)
+mktap bannerfish --port 81 --proxyhost=example.com
+
+twistd -f bannerfish.tap
+
+Now can work on internal server behind firewall
+
+If main server is Twisted Web, following Resource script will serve from bannerfish
+
+
+resource = proxy.ReverseProxyResource('localhost', 81, '/')
+
+
+Buffy (Halloween, season 2) -- You're sweet. A terrible liar, but sweet.
+Bannerfish -- Standalone Python
+
+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)
+
+
+Spike (Halloween, season 2) -- Shaking. Terrified. Alone. Lost little lamb.
+Bannerfish -- /etc/twisted-web/local.d Drop In
+
+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)
+
+
+Cordelia (Halloween, season 2) -- Well, I guess you better get them back to their parents.
+Bannerfish -- Resource Script
+
+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)
+
+But see later, about registry
+
+
+
+Xander (Innocence, season 2) -- They like to see the big guns.
+Bannerfish -- Distributed (Slave)
+
+
+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)
+
+Bannerfish -- Distributed (Master, Resource Script)
+
+
+from twisted.web import distrib
+
+resource = distrib.ResourceSubscription('unix',
+ '/var/run/bannerfish')
+
+
+Oz (Innocence, season 2) -- So, do you guys steal weapons from the Army a lot?
+Bannerfish -- Other options
+Mix and match possible
+
+Can serve same content multiple ways simultaneously
+
+Might be useful as a way to serve same ads different ways
+
+...or serve ads from several bannerfish servers...
+
+...each deployed differently.
+
+
+
+Buffy (Tabula Rasa, season 6) -- I'm like a superhero or something
+Bannerfish -- Conclusions
+What are the tradeoffs?
+
+Everything works in the simple cases
+
+Not enough complicated cases to have data
+
+Luckily, easy to move between them
+
+Motto -- move deployment choices as late as possible
+
+
+
+Giles (Killed By Death, season 2) -- Simple enough, but, but
+Further Reading
+Short overview -- doc/howto/web-overview.html
+
+In depth review -- doc/howto/using-twistedweb.html
+
+Using databases -- doc/howto/enterprise.html
+
+Deferred execution -- doc/howto/deferred.html
+
+Resource script examples -- doc/examples/*.rpy.py
+
+
+
+Giles (I Was Made to Love You, Season 5) -- There's an enormous amount of research we should do before -- no I'm lying
+Questions?
+Vampire Willow (Dopplegangland, season 3): Questions? Comments?
+Bonus Slides
+Xander (The Dark Age, season 2) -- A bonus day of class plus Cordelia.
+
+Python Configuration -- Hints
+Working with persistence
+
+Processors
+
+Indices
+
+Virtual Hosts
+
+
+
+Buffy (Phases, season 2) -- Have you dropped any hints?
+Python Configuration -- Persistence
+Don't define functions or classes
+
+Don't modify class attributes
+
+
+
+Spike (Once More, With Feeling) -- Let me rest in peace
+Python Configuration -- Processors
+
+
+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))
+
+
+Manny (Doublemeat Palace, season 6) -- It's a meat process
+Python Configuration -- Indices
+
+
+root = static.File("/var/www")
+root.indices = ['index.rpy', 'index.html']
+
+
+Willow (Buffy vs. Dracula, season 5) -- Labelling your amulets and indexing your diaries
+Python Configuration -- Virtual Hosts
+
+
+from twisted.web import vhost
+default = static.File("/var/www")
+foo = static.File("/var/foo")
+root = vhost.NamedVirtualHost(default)
+root.addHost('foo.com', foo)
+
+
+Fritz (I Robot, You Jane, season 1) -- The only reality is virtual.
+Python Configuration -- uber example
+
+
+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')
+
+
+Buffy (Potential, season 7) -- It was putting a lot of stock in that uber-vamp
+Python Configuration -- Splitting With Reverse Proxy
+
+
+from twisted.web import proxy
+
+root.putChild('foo',
+ proxy.ReverseProxyResource('localhost',
+ 81, '/foo/'))
+
+
+Buffy (Once More, With Feeling, season 6 -- So I will walk through the fire
+mktap examples
+mktap web
+
+mktap web --path=/var/www --logfile=/var/log/twistedweb.log
+
+mktap web --port=80 --path=/var/www --mime-type=text/plain
+
+mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl
+
+
+
+Anya (I Was Made to Love You, season 4) -- You can also see the website I designed for the magic shop
+mktap examples (cont'd)
+mktap web --users
+
+mktap web --ignore-ext=.cgi
+
+mktap web --index=index.cgi --index=index.rpy --index=index.html
+
+
+
+Buffy (Once More, With Feeling, season 6) -- All the twists and bends
+mktap examples (alternate formats)
+mktap --type=source web
+
+mktap --type=xml web
+
+
+
+Tara (Seeing Red, season 6) -- It isn't written in any ancient language we could identify.
+mktap examples (setting uid)
+mktap --uid=33 web
+
+mktap --gid=33 web
+
+Uid/Gid of www-data on Debian systems
+
+Not possible to use username
+
+More about this later
+
+
+
+Buffy (Who Are You?, season 4) -- I would be Buffy
+
+twistd examples
+twistd -f web.tap -l /var/log/twisted.log
+
+twistd -f web.tap --pidfile /var/run/web.pid
+
+twistd -x web.tax
+
+twistd -s web.tasFor mktap --type=source
+
+
+
+
+Xander (Teacher's Pet, season 1) -- How come *that* never came up?
+Shutting down twistd
+On Unix (in general):
+
+On Windows: Cannot daemonize on Windows, so just run twistd in a command prompt
+
+Switch to the command prompt, and press Control-C
+
+
+
+
+Buffy (Prophecy Girl, season 1) -- I don't wanna die.
+Shutdown TAPs
+Since TAPs store persistent data for an application, a 'shutdown' TAP is created on twistd shutdown
+
+You'll often want to start your Twisted application on subsequent runs with the shutdown TAP
+
+
+
+Headstone (The Gift, season 5) -- She saved the world. A lot.
+twistd and security
+When twistd is run as root, it will shed root privileges for the uid and gid of either the user that created the TAP or those specified on the mktap commandline.
+
+
+
+Buffy (Dopplegangland, season 3) -- I think it's good to be reliable
+
+Resource Call Examples
+/foo/bar/baz gets converted to:
+
+
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).render(request)
+
+
+Willow/Tara (Afterlife, Part 2, season 6) -- Child of words, hear thy makers
+Resource Call Examples (cont'd)
+/foo/bar/baz/ gets converted to:
+
+
+site.getChild('foo', request
+ ).getChild('bar', request
+ ).getChild('baz', request
+ ).getChild('', request
+ ).render(request)
+
+
+Buffy (Gone, season 6) -- Stop trying to see me.
+Distributed Servers -- Theory
+Master is a resource
+
+Slave is a server
+
+Same server can have both master and slave parts
+
+
+
+Anya (Once More, With Feeling, season 6) -- I've got a theory, it could be bunnies
+Distributed Servers -- Manually
+
+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)
+
+
+Buffy (Some Assembly Required, season 2) -- Men dig up the corpses and the women have the babies.
+Distributed Servers -- Manual (cont'd)
+
+
+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)
+
+
+Buffy (Welcome to the Hellmouth, season 1) -- Now, we can do this the hard way, or...
+Distributed Servers -- Manual (cont'd 2)
+
+
+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)
+
+
+Buffy (Welcome to the Hellmouth, season 1) -- ...well, actually there's just the hard way.
+Distributed Servers -- User Directory
+A resource
+
+Child that looks like 'moshez' -- ~moshez/public_html
+
+Child that looks like 'moshez.twistd' -- moshez's personal server
+
+
+
+Master (The Wish, season 3) -- Mass production!
+Distributed Servers -- User Directory Server
+With mktap: mktap web --user
+
+With Python configuration
+
+
+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)
+
+
+Richard (Reptile Boy, season 2) -- In his name.
+Distributed Servers -- Personal Servers
+With mktap: mktap web --personal ...
+
+With Python configuration
+
+
+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)
+
+
+Giles (Bargaining, season 6) -- It's my personal collection
+Debian Configuration
+Inside twisted-web package
+
+Goal -- look like other web servers to users
+
+Goal -- interoperate easily
+
+Goal -- allow users to avoid modifying files
+
+
+
+Buffy (Bad Girls, season 3) -- We can help each other.
+Debian Configuration -- Usage
+Changing port -- edit /etc/twisted-web/ports
+
+Want to use behind reverse proxy? Use rptwisted
+
+Change anything else -- drop files in /etc/twisted-web/local.d
+
+
+
+Faith (Home Coming, season 3) -- we'll use 'em
+Debian Configuration -- Drop In Examples
+
+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)))
+
+
+Buffy (The Freshman, season 4) -- I just thought I'd drop in
+Debian Configuration -- Drop In Examples (cont'd)
+
+from twisted.web import script, static
+
+default.processors['.rpy'] = script.ResourceScript
+default.ignoreExt('rpy')
+
+
+Riley (As You Were, season 6) -- Sorry to just drop in on you
+Debian Configuration -- Drop In Examples (cont'd 2)
+
+from twisted.web import vhost
+
+default.putChild('vhost', vhost.VHostMonsterResource())
+
+
+Sam (As You Were, season 6) -- a hairy night drop into hostile territory
+twistedmatrix.com Configuration
+
+
+...
+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)
+
+
+Xander (The Witch, season 1) -- May all lesser cretins bow before me.
+Apache vs. Twisted Web
+Apache is faster
+
+Apache -- Threads/processes model
+
+Twisted -- async model
+
+Apache -- has C security holes (buffer overflows)
+
+Twisted -- easy to set up
+
+Twisted -- built in Python programmability
+
+
+
+Willow (Buffy vs. Dracular, season 5) -- I think we've just put our finger on why we're the sidekicks
+Apache/Twisted Web Integration
+Use both!
+
+Apache's reverse proxy works well
+
+Easy to have a site which is partially managed by Apache
+
+Documentation has examples of configurations
+
+
+
+Xander (What's My Line, season 2) -- Angel's our friend! Except I don't like him.
+Zope vs. Twisted Web
+Zope -- fully editable through the web
+
+Zope -- uses ZODB, not file system
+
+Twisted -- can integrate with other protocols easily
+
+Twisted -- extension code has much less overhead
+
+
+
+Willow (Dopplegangland, season 3) -- Competition is natural and healthy
+Zope/Twisted Web Integration
+Possible to use Twisted as Zope's network layer
+
+Hackish with Zope2
+
+Easier with Zope3
+
+
+
+Snyder (Dopplegangland, season 3) -- It's a perfect match.
+Zope/Twisted Web Integration (cont'd)
+Less direct -- use Apache
+
+Reverse proxy parts to Zope
+
+Reverse proxy parts to Twisted Web
+
+
+
+Wesley (Dopplegangland, season 3) -- Still a little sloppy, though
+Applications Appropriate for Twisted Web
+Webmail
+
+Blogs
+
+Web/other protocol chat systems
+
+
+
+Sweet (Once More, With Feeling) -- Why don't you come and play?
+Behind Reverse Proxy
+Sometimes, we want Twisted to pretend to be another host/port
+
+Reverse proxies, NATs, etc.
+
+Reverse proxy to /vhost/http/<host:port>/
+
+Make sure root has a child called vhost of type twisted.web.vhost.VirtualHostingMonster
+
+
+
+Jenny (I Robot -- You Jane, season 1) -- The divine exists in cyberspace
+Rewrite Rules
+Change a URL to another
+
+Useful for different treatment from outside resources
+
+Wraps a resource
+
+
+
+Spike (What's My Line, season 2) -- Read it again.
+Rewrite Rules -- Example
+
+
+root = static.File("/var/www")
+root.putChild("users", distrib.UserDirectory())
+root = rewrite.RewriterResource(root, rewrite.tildeToUsers)
+
+
+
+Spike (What's My Line, season 2) -- I think it's just enough kill.
+websetroot
+Used to change what the root of the server points to
+
+Set it to a Resource contained either in a Python source file or a Pickle file
+
+
+
+Manny (DoubleMeat Palace, season 6) -- We have a lot of turnover here
+Sample websetroot command lines
+websetroot -p 80 -f web.tap --script rootResource.py
+
+websetroot -p 8080 -f web.tap --pickle rootPickle
+
+
+
+Manny (DoubleMeat Palace, season 6) -- You can toss it
+init.d
+pidfile
+
+chdir
+
+chroot?
+
+No smooth reloading
+
+Persistence
+
+
+
+Bob (Zeppo, season 3) -- He hasn't been initiated.
+Special Bonus - How to Configure <user>.example.com
+
+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 self.users.has_key(username):
+ 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)
+
+
+Morgan (The Puppet Show, season 1) -- Weird? What d'you mean?
+Special Bonus - How to Configure <user>.example.com (cont'd)
+Put above in a module (say, uservhost)
+
+Use following configuration file
+
+
+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)
+
+
+Snyder (The Puppet Show, season 1) -- You need to integrate into this school, people.
+Using the Twisted Registry
+Use especially in Resource Scripts
+
+Save persistent information
+
+Uses Twisted's Componentized to be extensible
+
+
+
+Angel (Helpless, season 3) -- I wanted to keep it safe
+Using the Twisted Registry -- example
+
+
+from twisted.web import distrib
+
+resource = registry.getComponent(distrib.UserDirectory)
+if not resource:
+ resource = distrib.UserDirectory()
+ registry.setComponent(distrib.UserDirectory, resource)
+
+
+Paul (The Freshman, season 4) -- Do you know where they're distributing the [...] applications?
+Using the Twisted Registry -- problems
+In most cases -- need to write a custom class
+
+Saves data in-memory
+
+Won't work as expected unless -shutdown taps are used
+
+
+
+Anya (Once More, With Feeling, season 6) -- The only trouble is [pause] I'll never tell.
+Alternative Configuration Formats -- XML
+Can be generated from mktap
+
+Editable with any XML editor
+
+Easy to do easy things
+
+Nontrivial to do hard things
+
+
+
+Buffy (Once More, With Feeling, season 6) -- To fit in in this glittering world.
+Alternative Configuration Formats -- XML -- example
+
+<?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>
+
+
+Natalie (Teacher's Pet, season 1) -- There's nothing ugly about these creatures
+Alternative Configuration Formats -- Source
+Can be generated from mktap
+
+Editable with any Python source editor
+
+Easy to do easy things
+
+Nontrivial to do hard things
+
+
+
+Willow/Giles/Xander (Primeval, season 4) -- You could never hope to grasp the source
+Alternative Configuration Formats -- Source -- Example
+
+app=Ref(1,
+ Instance('twisted.internet.app.Application',{
+...
+ 'tcpPorts':[
+ (
+ 80,
+ Instance('twisted.web.server.Site',
+...
+ resource=Instance('twisted.web.static.File',{
+...
+ 'path':'/var/www',
+...
+ ),
+ ],
+...
+ }))
+
+
+Tara (Family, season 5) -- You learn her source, and, uh we'll introduce her to her insect reflection
+Twisted Web - Beginnings
+
+
+<glyphAtWork> the http server was so we could say "Web!" if we ever did
+ a freshmeat announcement
+<glyphAtWork> this makes people excited
+
+Turned out he was right
+
+
+
+Dawn (Get It Done, season 7) -- I think it's an origin myth.
+Woven Overview
+HTML templates
+
+Model/View/Controller architecture
+
+Integrated with deferred
+
+Classical systems work badly with async
+
+More -- beyond scope of this tutorial
+
+
+
+Razor (Bargaining, season 6) -- A pretty toy
+
diff --git a/doc/historic/2003/europython/twisted.html b/doc/historic/2003/europython/twisted.html
new file mode 100644
index 0000000..2576bca
--- /dev/null
+++ b/doc/historic/2003/europython/twisted.html
@@ -0,0 +1,608 @@
+Twisted Tutorial
+
+
+Twisted Tutorial
+
+Twisted -- The Tutorial
+Welcome
+
+Gimmick -- Charmed quotes
+
+
+
+Prue (Something Wicca This Way Comes, season 1) -- Piper, the girl has no vision, no sense of the future.
+Twisted -- Networking For Python
+Handles the icky socket stuff
+
+Handles the icky select stuff
+
+No threads, no blocking
+
+
+
+Leo (Bite Me, season 4) -- As far as I know they're apart of a whole different network now.
+Finger
+Send username
+
+Get back some stuff about user
+
+Will only implement subset of protocol here
+
+
+
+Natalie (Blinded By the Whitelighter) -- I'll assume a demon attacked your finger
+Finger - Protocol code
+
+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
+
+
+Phoebe (Blind Sided, season 1) -- Standard dating protocol.
+Finger - client factory
+Keep configuration information
+
+In this case, just the username
+
+
+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
+
+
+
+Jack (Ms. Hellfire, season 2) -- Well, they'd better be a rich client
+Finger - tying it all together
+Actually run above code
+
+Use reactors
+
+
+from twisted.internet import reactor
+import sys
+
+user, host = sys.argv[1].split('@')
+port = 79
+reactor.connectTCP(host, port, FingerFactory(port))
+reactor.run()
+
+
+Prue/Phoebe/Piper (Something Wicca This Way Comes, season 1) -- The power of three will set us free
+Finger - a bug
+Succeed or fail, program doesn't exit
+
+Reactor continues in a loop
+
+Takes almost no CPU time...
+
+...but still wrong behaviour
+
+
+
+Leo (Trial By Magic, season 4) -- Demons you can handle but not rats?
+Digression - Deferreds
+In order to be more flexible, we want callbacks
+
+Common callbacks are too weak
+
+We used 'deferreds' as an abstraction for callbacks
+
+
+
+Piper (Morality Bites, season 2) -- Talk about it later.
+Finger - reimplementing correctly
+
+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)
+
+
+
+Finger - reimplementing correctly (cont'd)
+
+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)
+
+
+Finger - reimplementing correctly (cont'd 2)
+
+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()
+
+
+Phoebe (Charmed and Dangerous, season 4) -- That's what we were missing.
+Servers
+Servers are actually easier
+
+Servers meant to wait for events
+
+Most of concepts similar to clients
+
+
+
+Genie (Be Careful What You Witch For, season 2) -- All I know is that you rubbed and now I serve.
+Finger - protocol
+
+class FingerServer(basic.LineReceiver):
+
+ def lineReceived(self, line):
+ self.transport.write(self.factory.getUser(line))
+ self.transport.loseConnection()
+
+
+Secretary (The Painted World, season 2) -- Well, you won't have any trouble with this if you figured that out.
+Finger - factory
+
+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
+
+
+Prue (The Demon Who Came In From the Cole, season 3) -- Okay, so who are they?
+Finger - glue
+
+factory = FingerServerFactory()
+factory.setUser("moshez", "Online - Sitting at computer\n")
+factory.setUser("spiv", "Offline - Surfing the waves\n")
+
+reactor.listenTCP(79, factory)
+
+
+Prue (All Halliwell's Eve, season 3) -- Put it all together, it may just work.
+Finger Server - problem
+What if server has to actually work to find user's status?
+
+For example, read status from a website
+
+API forces us to block -- not good
+
+
+
+Piper (All Halliwell's Eve, season 3) -- We've got big problems, a little time and a little magic.
+Finger server -- new protocol
+
+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()
+
+
+Piper (Ex Libris, season 2) -- We'll worry about it later.
+Finger - factory
+
+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
+
+
+Piper/Zen Master (Enter the Demon, season 4) -- It is a different realm down there with new rules.
+Finger - web factory
+
+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
+
+
+Applicant #3 (The Painted World, season 2) -- in this day and age, who can't write in the HTML numeric languages, right?
+Application
+The Twisted way of configuration files
+
+Decouple configuration from running
+
+Application (Example)
+
+# 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)
+
+
+
+Paige (Hell Hath No Fury, season 4) -- I am taking full responsibility for being late with the application.
+twistd
+TWISTed Daemonizer
+
+Daemonizes Twisted servers
+
+Takes care of log files, PID files, etc.
+
+twistd -y finger.tpy
+
+
+
+Phoebe (Sleuthing With the Enemy, season 3) -- Was it some sick twisted demonic thrill?
+twistd examples
+twistd -y finger.tpy -l /var/finger/log
+
+twistd -y finger.tpy --pidfile /var/run/finger.pid
+
+twistd -y finger.tpy --chroot /var/run
+
+
+
+Professor Whittlessy (Is There a Woogy In the House?, season 1) -- I use your house as an example
+Writing Plugins
+Automatically create application configurations
+
+Accessible via commandline or GUI
+
+Writing Plugins (Example)
+
+# 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"),)
+
+
+
+Writing Plugins (Example cont'd)
+
+def updateApplication(app, config):
+ f = FingerFactory()
+ for (user, status) in config.users:
+ f.setUser(user, status)
+ app.listenTCP(int(config.opts['port']), s)
+
+
+Paige (Bite Me, season 4) -- They won't join us willingly.
+Writing Plugins (Example cont'd 2)
+
+# File finger/plugins.tml
+register("Finger",
+ "finger.tap",
+ description="Finger Server",
+ type='tap',
+ tapname="finger")
+
+
+Queen (Bite Me, season 4) -- That's what families are for.
+Using mktap
+mktap finger --user moshez --user spiv=Offline
+
+twistd -f finger.tap
+
+
+
+Piper (Charmed and Dangerous, season 4) -- We'll use potions instead.
+Delayed execution
+Basic interface: reactor.callLater(<time>, <function>, [<arg>, [<arg> ...]])
+
+reactor.callLater(10, reactor.stop)
+
+reactor.callLater(5, util.println, 'hello', 'world')
+
+
+
+Cole (Enter the Demon, season 4) -- I know, but not right now.
+callLater(0,) -- An idiom
+Use to set up a call in next iteration of loop
+
+Can be used in algorithm-heavy code to let other code run
+
+
+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()
+
+
+Piper (Lost and Bound, season 4) -- Someone, I won't say who, has the insane notion
+UNIX Domain Sockets
+Serversreactor.listenUNIX('/var/run/finger.sock', FingerWebFactory())
+
+
+Clientsreactor.connectUNIX('/var/run/finger.sock', FingerFactory())
+
+
+
+
+Kate (Once Upon a Time, season 3) -- Fairies don't talk the same way people do.
+SSL Servers
+
+
+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())
+
+
+SSL Clients
+
+
+from twisted.internet import ssl
+
+reactor.connectSSL(111, 'localhost', FingerFactory(), ssl.ClientContextFactory())
+
+
+Natalie (Blinded By the Whitelighter, season 3) -- I mean, in private if you wouldn't mind
+Running Processes
+A process has two outputs: stdout and stderr
+
+Protocol to interface with it is different
+
+
+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"])
+
+
+Prue (Coyote Piper, season 3) -- You have to know that you can talk to me
+Further Reading
+
+Phoebe (Animal Pragmatism, season 2) -- Ooh, the girls in school are reading this.
+Questions?
+Piper (Something Wicca This Way Comes, season 1) -- Tell me that's not our old spirit board?
+Bonus Slides
+Prue (Sleuthing With the Enemy, season 3) -- All right, you start talking or we start the bonus round.
+Perspective Broker
+Meant to be worked async
+
+Can transfer references or copies
+
+Secure (no pickles or other remote execution mechanisms)
+
+Lightweight (bandwidth and CPU)
+
+Translucent
+
+
+
+Paige (Charmed Again, season 4) -- I guess I just kind of feel - connected somehow.
+PB Remote Control Finger (Server)
+
+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))
+
+
+Piper (Be Careful What You Witch For, season 2) -- Okay, you think you can control the power this time?
+PB Remote Control Finger (Client)
+
+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()
+
+
+Leo (Be Careful What You Witch For, season 2) -- How about you just keep your arms down until you learn how to work the controls.
+Perspective Broker (Trick)
+Add to the application something which will call reactor.stop()
+
+Portable (works on Windows)
+
+Gets around OS security limitations
+
+Need to add application-level security
+
+The docs have the answers (see 'cred')
+
+
+
+Piper (Lost and Bound, season 4) -- They're not good or bad by themselves, it's how we use them
+Perspective Broker (Authentication)
+pb.cred
+
+Perspectives
+
+Can get remote user with every callInherit from pb.Perpsective
+
+Call methods perspective_<name>(self, remoteUser, ...)
+
+
+
+
+Piper (She's a Man, Baby, a Man!, season 2) -- Okey-Dokey. I get the point.
+
+Perspective Broker - About Large Data Streams
+
+
+
+Sending large (>640kb) strings is impossible -- feature, not bug.
+
+It stops DoSes
+
+Nobody would ever need...
+
+Use twisted.spread.utils.Pager -- sends the data in managable chunks.
+
+
+
+
+Piper (Womb Raider, season 4) --
+Oral tradition tales of a giant whose body served as a portal to other
+dimensions.
+
+Producers and Consumers
+Use for things like sending a big file
+
+A good alternative to manually reactor.callLater(0,)-ing
+
+See twisted.internet.interfaces.{IProducer,IConsumer}
+
+
+
+Phoebe (Black as Cole, season 4) -- Apparently he feeds on the remains of other demons' victims.
+Threads (callInThread)
+Use for long running calculations
+
+Use for blocking calls you can't do without
+
+deferred = reactor.callInThread(function, arg, arg)
+
+
+
+Piper (The Painted World, season 2) -- There will be consequences. There always are.
+Threads (callFromThread)
+Use from a function running in a different thread
+
+Always thread safe
+
+Interface to non-thread-safe APIs
+
+reactor.callFromThread(protocol.transport.write, s)
+
+
+
+Phoebe (Witch Trial, season 2) -- Maybe it's still in the house. Just on different plane.
+
+Using ApplicationService
+Keep useful data...
+
+...or useful volatile objects
+
+Support start/stop notification
+
+Example: process monitor
+
+
+
+Phoebe (Marry Go Round, season 4) -- Yeah, that's just in case you need psychic services.
+
+Playing With Persistence
+Shutdown taps are useful
+
+Even if you use twistd -y
+
+So rememberClasses belong in modules
+
+Functions belong in modules
+
+Modifying class attributes should be avoided
+
+
+
+
+Cole (Marry Go Round, season 4) -- That Lazerus demon is a time bomb waiting to explode
+
diff --git a/doc/historic/2003/europython/webclients.html b/doc/historic/2003/europython/webclients.html
new file mode 100644
index 0000000..8a26f71
--- /dev/null
+++ b/doc/historic/2003/europython/webclients.html
@@ -0,0 +1,482 @@
+Writing Web Clients
+
+Writing Web Clients
+
+Web Clients -- The Tutorial
+Welcome
+
+Gimmick -- Buffy quotes
+
+
+
+Anya (Family, season 5) -- Thank you for coming. We value your patronage.
+What Are Web Clients?
+Clarification: non-interactive web clients
+
+Special purpose
+
+Often, quick and dirty hacks
+
+Make a web page into API
+
+
+
+Giles (Family, season 5) -- Could we please be a little less effusive, Anya?
+What Are Web Clients Useful For?
+Mass download
+
+Periodic checking
+
+Automating tasksMake a web page more friendly
+
+
+
+
+Harmony (Family, season 5) -- Aww. You're my little lamb.
+Review of Modules
+htmllib
+
+sgmllib
+
+httplib
+
+urllib
+
+urllib2
+
+urlparse
+
+
+
+Buffy (Family, season 5) -- Your definition of narrow is impressively wide.
+Modules -- htmllib
+Most useful for easy filtering of images
+
+...or links
+
+Other things often easier with sgmllib
+
+Or with re
+
+Or with string manipulation
+
+
+
+Xander (Family, season 5) -- The answer is somewhere here.
+Modules -- htmllib -- idiomatic usage
+
+# For lists
+import htmllib, formatter
+
+h = htmllib.HTMLParser(formatter.NullFormatter())
+h.feed(htmlString)
+print h.anchorlist
+
+
+
+Xander (Family, season 5) -- I'm helping, I'm reading, I'm quiet.
+Modules -- htmllib -- idiotmatic usage (cont'd)
+
+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
+
+
+
+Donny (Family, season 5) -- Look what I found!
+Modules -- htmllib -- base
+Some sites use 'base' for different relative linking
+
+For example, Zope does
+
+In above examples, 'h.base' has the base
+
+
+
+Dawn (Family, season 5) -- This is the source of my gladness.
+Modules -- htmllib -- base (example)
+If the page on http://example.com/foo/bar.html has a link to '../baz.html'It means http://example.com/baz.html
+
+
+If the original page has base='/foo/quux'It means http://example.com/foo/baz.html
+
+
+
+
+Riley (Family, season 5) -- Every time I think I'm getting close to you...
+Modules -- urllib/urllib2
+High-level interface
+
+Treat URLs as file-like objects
+
+...but still allows low-level operations
+
+Interface largely compatible
+
+
+
+Glory (Family, season 5) -- I am great and I am beautiful.
+Modules -- urllib/urllib2 (cont'd)
+Can work through object-interface
+
+More flexible
+
+Interface no longer compatible
+
+urllib2 better usually
+
+
+
+Joyce (Ted, season 2) -- He redid my entire system.
+Modules -- urllib/urllib2 (examples)
+urllib.urlopen("http://www.yahoo.com/").read() -> contents
+
+urllib.urlopen("http://www.yahoo.com/").info() -> headers
+
+Same works with urllib2
+
+Automatically uses environment variables for proxies
+
+urllib2 supports proxies with authentication
+
+
+
+Xander (Ted, season 2) -- Yum-my!
+Digression -- HTTP Overview
+Request/Response
+
+Request is command followed by headers followed by body
+
+Response is error code followed by headers followed by body
+
+No welcome message
+
+
+
+Tara (Family, season 5) -- ...in terms of the karmic cycle.
+Example HTTP Sessions
+
+
+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>
+
+
+
+Giles (Family, season 5) -- And you are talking about what on earth?
+Modules -- httplib
+Low-level interface to innards of HTTP
+
+Absolute control
+
+No abstractions
+
+
+
+Mr. MacLay (Family, season 5) -- We know how to control her...problem.
+Modules -- httplib -- example
+Note: usually, the Host header is important
+
+
+>>> 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"
+
+
+Anya (Family, season 5) -- ...and it was fun!
+Modules -- urlparse
+urlparse.urljoin -- like os.path.join for URLs
+
+For path manipulationurlparse.urlsplit
+
+urlparse.urlunsplit
+
+
+
+
+Buffy (Family, season 5) -- You know what, you guys, just leave it here.
+Downloading Dilbert
+
+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")
+
+
+Tara (Family, season 5) -- That was funny if you [...] are a complete dork.
+Downloading Dark Angel Transcripts
+Common situation of mass download
+
+
+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))
+
+
+
+Intern (Family, season 5) -- Yeah. That makes like five this month.
+Downloading Dark Angel Transcripts (select)
+
+
+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)
+
+
+Joyce (Ted, season 2) -- I've been looking for the right moment.
+Downloading Dark Angel Transcripts (select, cont'd)
+Same code up to 'really download'
+
+
+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)
+
+
+Buffy (Family, season 5) -- Tara's damn birthday is just one too many things for me to worry about.
+Downloading Dark Angel Transcripts (threads)
+
+
+import threading
+
+for link in links:
+ Thread(target=urllib2.urlretrieve,
+ args=(link,posixpath.basename(link)))
+
+
+Buffy (Ted, season 2) -- Sounds like fun.
+Digression - twisted.web.client
+Part of the Twisted networking framework
+
+High level interface to HTTP client
+
+Completely asynchronous
+
+Reports results via callbacks
+
+client.getpage("http://www.yahoo.com").addCallbacks(gotResult, gotError)
+
+
+
+Buffy (Ted, season 2) -- You're supposed to use your powers for good!
+Downloading Dark Angel Transcripts (web.client)
+
+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()
+
+
+Ted (Ted, season 2) -- You don't have to worry about anything.
+HTTP Authentication
+Client attempts to connect
+
+Server sends back a 401 (please authenticate)
+
+Client sends same request back -- with auth tokens
+
+Only HTTP Basic authentication widely supported
+
+Client can send auth tokens on more requests automatically
+
+
+
+Buffy (Ted, season 2) -- Ummm... Who are these people?
+HTTP Authentication - manually
+In HTTP, authentication is a header
+
+Base authentication is sending username and password
+
+
+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()
+
+
+Tara (Family, season 5) -- And, uh, these are my-my friends.
+HTTP Authentication - urllib2
+Can read username/password from URL
+
+urllib2.urlopen("http://moshez:s3krit@example.com"
+ "/protected/stuff.html")
+
+
+
+Xander (Ted, season 2) -- I am really jinxing the hell out of us.
+Further Reading
+
+Willow (Ted, season 2) -- 'Book-cracker Buffy', it's kind of her nickname.
+Questions?
+Buffy (Family, season 5) -- I let you come, now sit down and look studious.
+Bonus Slides
+Tara (Family, season 5) -- You always make me feel special.
+
+Cookies
+Carry state from one page to another
+
+Server sends header: Set-Cookie
+
+Client sends on later requests header: Cookie
+
+
+
+Ted (Ted, season 2) -- Who's up for dessert? I made chocolate-chip cookies!
+urllib2 cookies
+Unfortunately, no automatic cookie jar support
+
+Can manually use .info() to read cookies...
+
+...and the Request() API to send them to the server
+
+
+
+Joyce (Ted, season 2) -- Mm! Buffy, you've got to try one of these!
+Logging Into Advogato
+
+
+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()
+
+
+
+Anya (Family, season 5) -- I have a place in the world now.
+On Being Nice - Robots
+Some sites don't want automatic crawlers
+
+It is up to you whether to play nice
+
+But you should know the rules before you break them
+
+Robots file -- at /robots.txt
+
+
+
+Willow (Ted, season 2) -- There were design features in that robot that pre-date...
+Using robotparser
+
+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)
+
+
+
+Buffy (Ted, season 2) -- Tell me you didn't keep any parts.
+webchecker
+In the source distribution, in Tools/
+
+Understands robots.txt
+
+Can override which links gets chased
+
+
+
+Willow (Ted, season 2) -- What do you mean, check him out?
+websucker
+In the source distribution, in Tools/
+
+Uses webchecker as a module
+
+Saves the pages it downloads
+
+
+
+Buffy (Ted, season 2) -- Find out his secrets, hack into his life.
+
+
diff --git a/doc/historic/2003/haifux/haifux.html b/doc/historic/2003/haifux/haifux.html
new file mode 100644
index 0000000..255178c
--- /dev/null
+++ b/doc/historic/2003/haifux/haifux.html
@@ -0,0 +1,2235 @@
+Evolution of Finger
+Evolution of Finger
+
+Refuse Connections
+
+
+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.
+
+Do Nothing
+
+
+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.
+
+Drop Connections
+
+
+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.
+
+Read Username, Drop Connections
+
+
+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.
+
+
+Read Username, Output Error, Drop Connections
+
+
+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 :)
+
+Output From Empty Factory
+
+
+# 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.
+
+Output from Non-empty Factory
+
+
+# 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.
+
+Use Deferreds
+
+
+# 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...
+
+Run 'finger' Locally
+
+
+# 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 Status from the Web
+
+
+# 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.
+
+
+Use Application
+
+
+# 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.
+
+twistd
+
+
+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.
+
+Setting Message By Local Users
+
+
+# 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
+
+
+Use Services to Make Dependencies Sane
+
+
+# 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 Status File
+
+
+# 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.
+
+Announce on Web, Too
+
+
+# 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.
+
+Announce on IRC, Too
+
+
+# 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.
+
+
+
+Add XML-RPC Support
+
+
+# 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.
+
+
+Write Readable Code
+
+
+# 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.
+
+Write Maintainable Code
+
+
+# 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.
+
+
+
+Advantages of Latest Version
+
+
+Readable -- each class is short
+Maintainable -- each class knows only about interfaces
+Dependencies between code parts are minimized
+Example: writing a new IFingerService is easy
+
+
+
+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.
+
+Another Backend
+
+
+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.
+
+Yet Another Backend: Doing the Standard Thing
+
+
+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!
+
+
+Aspect Oriented Programming
+
+
+This is an example...
+...with something actually useful...
+...not logging and timing.
+Write less code, have less dependencies!
+
+
+Use Woven
+
+
+# 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.
+
+Use Perspective Broker
+
+
+# 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.
+
+Support HTTPS
+
+
+# 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.
+
+Finger Proxy
+
+
+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.
+
+Organization
+
+
+Code belongs in modules: everything above the application=
+ line.
+Templates belong in separate files. The templateFile attribute can be
+ used to indicate the file.
+The templateDirectory attribute will be used to indicate where to look
+ for the files.
+
+
+
+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))
+
+
+
+Seperaration between: code (module), configuration (file above),
+ presentation (templates), contents (/etc/users), deployment (twistd)
+Examples, early prototypes don't need that.
+But when writing correctly, easy to do!
+
+
+Easy Configuration
+
+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 kwargs.has_key('ircnick'):
+ i = IIRCClientFactory(f)
+ i.nickname = kwargs['ircnick']
+ ircServer = kwargs['ircserver']
+ application.connectTCP(ircserver, 6667, i)
+ if kwargs.has_key('pbport'):
+ 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:
+
+
+Spaghetti: each piece of code interacts with every other piece of
+ code [can be implemented with GOTO, functions, objects]
+Lasagna: code has carefully designed layers. Each layer is, in
+ theory independent. However low-level layers usually cannot be
+ used easily, and high-level layers depend on low-level layers.
+Raviolli: each part of the code is useful by itself. There is a thin
+ layer of interfaces between various parts [the sauce]. Each part
+ can be usefully be used elsewhere.
+...but sometimes, the user just wants to order "Raviolli", so one
+ coarse-grain easily definable layer of abstraction on top of it all
+ can be useful.
+
+
+Plugins
+
+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
+
+
+OS Integration
+
+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!
+
+Summary
+
+
+Twisted is asynchronous
+Twisted has implementations of every useful protocol
+In Twisted, implementing new protocols is easy [we just did three]
+In Twisted, achieving tight integration of servers and clients
+ is easy.
+In Twisted, achieving high code usability is easy.
+Ease of use of Twisted follows, in a big part, from that of Python.
+Bonus: No buffer overflows. Ever. No matter what.
+
+
+Motto
+
+
+"Twisted is not about forcing. It's about mocking you when you use
+ the technology in suboptimal ways."
+You're not forced to use anything except the reactor...
+...not the protocol implementations...
+...not application...
+...not services...
+...not components...
+...not woven...
+...not perspective broker...
+...etc.
+But you should!
+Reinventing the wheel is not a good idea, especially if you form
+ some vaguely squarish lump of glue and poison and try and attach
+ it to your car.
+The Twisted team solved many of the problems you are likely to come
+ across...
+...several times...
+...getting it right the nth time.
+
+
+
+
diff --git a/doc/historic/2003/haifux/notes.html b/doc/historic/2003/haifux/notes.html
new file mode 100644
index 0000000..c35afa8
--- /dev/null
+++ b/doc/historic/2003/haifux/notes.html
@@ -0,0 +1,60 @@
+Notes
+Notes
+
+[translated roughly from Hebrew]
+
+Introduction
+
+
+Name: Moshe Zadka
+Twisted developer [Debian, Python]
+Not:
+XML talk (XML is: standarised, flexibl, internationalized)
+Gettysburg in Power Point
+Touching lots of things briefly
+
+How to do more than one thing at once?
+Fork (Apache)
+Thread (AOLServer)
+Cheat (GUI programs)
+
+Main loop calling our code.
+Let's develop a network program!
+
+
+Discussion
+
+
+What is blocking?
+
+There is a UNIX concept of blocking...
+...which is not really relevant.
+Connecting to an accepting UNIX domain socket is blocking...
+...reading a file from NFS is not.
+
+Wait a minute: why is that interesting?
+GUI -- humans (0.1s-1s)
+Network: connections might get refused
+
+Typical scenario: listen buffer 5, 1e6 connections/day --
+ don't dawdle for more than 0.08s
+These are the numbers that matter!
+Useful criterion: blocking==takes more than 0.01s on normal load.
+Depends on hardware, etc.
+Real world :(
+But a useful rule of thumb when coding.
+Trick: reactor.callLater(0,)
+Continuation-passing-style, tail-call-optimization
+But not pure -- not optimal
+
+
+References
+
+
+Plonk
+twistedmatrix.com
+mailing list
+irc -- #twisted
+
+
+
diff --git a/doc/historic/2003/pycon/applications/applications b/doc/historic/2003/pycon/applications/applications
new file mode 100755
index 0000000..a6c18a2
--- /dev/null
+++ b/doc/historic/2003/pycon/applications/applications
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL, Image, PRE
+from twslides import Lecture
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return ' %s ' % (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/doc/historic/2003/pycon/applications/applications.html b/doc/historic/2003/pycon/applications/applications.html
new file mode 100644
index 0000000..f357d7f
--- /dev/null
+++ b/doc/historic/2003/pycon/applications/applications.html
@@ -0,0 +1,343 @@
+
+
+
+
+
+ Applications of the Twisted Framework
+
+
+
+ Applications of the Twisted Framework
+ Jp Calderone
+ exarkun@twistedmatrix.com
+
+ ABSTRACT
+
+ 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.
+
+ Twisted (dot) Names
+
+ Motivation
+ 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 Server
+ 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.
+
+ Names Client
+ 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.
+
+ Implementation
+
+ 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.
+
+ Shortcomings
+
+ 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.
+
+ No attempt has been made to implement DNSSEC.
+
+ Certain areas of the server remain out of compliance with the
+ standardized RFCs, occasionally causing undesirable behavior when
+ interacting with clients. This most frequently manifests itself as a
+ lookup which fails the first time and succeeds on subsequent attempts.
+ It is not believed that these represent architectural flaws, only small
+ oversights in areas such as the "additional processing" sections of the
+ current authority resolver implementations.
+
+ Pynfo
+
+ Motivation
+ 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.
+
+ Architecture
+ 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.
+
+
+
+ Employing Components
+ 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.
+
+ The Plugin Framework
+
+ 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/doc/historic/2003/pycon/applications/pynfo-chart.png b/doc/historic/2003/pycon/applications/pynfo-chart.png
new file mode 100644
index 0000000..7318b15
Binary files /dev/null and b/doc/historic/2003/pycon/applications/pynfo-chart.png differ
diff --git a/doc/historic/2003/pycon/conch/conch b/doc/historic/2003/pycon/conch/conch
new file mode 100755
index 0000000..00ed511
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conch
@@ -0,0 +1,98 @@
+#!/usr/bin/python
+from slides import Slide, Bullet, SubBullet, URL
+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/doc/historic/2003/pycon/conch/conch.html b/doc/historic/2003/pycon/conch/conch.html
new file mode 100644
index 0000000..0faab7e
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conch.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+Twisted Conch: SSH in Python with Twisted
+
+
+
+Twisted Conch: SSH in Python with Twisted
+
+
+
+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/doc/historic/2003/pycon/conch/conchtalk.txt b/doc/historic/2003/pycon/conch/conchtalk.txt
new file mode 100755
index 0000000..d2552ee
--- /dev/null
+++ b/doc/historic/2003/pycon/conch/conchtalk.txt
@@ -0,0 +1,144 @@
+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/doc/historic/2003/pycon/conch/smalltwisted.png b/doc/historic/2003/pycon/conch/smalltwisted.png
new file mode 100644
index 0000000..4f7d04d
Binary files /dev/null and b/doc/historic/2003/pycon/conch/smalltwisted.png differ
diff --git a/doc/historic/2003/pycon/conch/twistedlogo.png b/doc/historic/2003/pycon/conch/twistedlogo.png
new file mode 100644
index 0000000..6226297
Binary files /dev/null and b/doc/historic/2003/pycon/conch/twistedlogo.png differ
diff --git a/doc/historic/2003/pycon/deferex/deferex-bad-adding.py b/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
new file mode 100644
index 0000000..d124eaa
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/doc/historic/2003/pycon/deferex/deferex-chaining.py b/doc/historic/2003/pycon/deferex/deferex-chaining.py
new file mode 100644
index 0000000..cee9bea
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-chaining.py
@@ -0,0 +1,13 @@
+from twisted.internet import reactor, defer
+
+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/doc/historic/2003/pycon/deferex/deferex-complex-failure.py b/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
new file mode 100644
index 0000000..bcc38e2
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
@@ -0,0 +1,30 @@
+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/doc/historic/2003/pycon/deferex/deferex-complex-raise.py b/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
new file mode 100644
index 0000000..8005e45
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
@@ -0,0 +1,12 @@
+class MyExc(Exception):
+ "A sample exception."
+
+try:
+ x = 1 + 3
+ raise MyExc("I can't go on!")
+ x = x + 1
+ print x
+except MyExc, me:
+ print 'error (',me,'). x was:', x
+except:
+ print 'fatal error! abort!'
diff --git a/doc/historic/2003/pycon/deferex/deferex-forwarding.py b/doc/historic/2003/pycon/deferex/deferex-forwarding.py
new file mode 100644
index 0000000..c2fa6f9
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-forwarding.py
@@ -0,0 +1,9 @@
+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/doc/historic/2003/pycon/deferex/deferex-listing0.py b/doc/historic/2003/pycon/deferex/deferex-listing0.py
new file mode 100644
index 0000000..e0de3ce
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing0.py
@@ -0,0 +1,18 @@
+
+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/doc/historic/2003/pycon/deferex/deferex-listing1.py b/doc/historic/2003/pycon/deferex/deferex-listing1.py
new file mode 100644
index 0000000..399eb2b
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing1.py
@@ -0,0 +1,6 @@
+def prettyRequest(server, requestName):
+ return server.makeRequest(requestName
+ ).addCallback(
+ lambda result: ', '.join(result.asList())
+ ).addErrback(
+ lambda failure: failure.printTraceback())
diff --git a/doc/historic/2003/pycon/deferex/deferex-listing2.py b/doc/historic/2003/pycon/deferex/deferex-listing2.py
new file mode 100644
index 0000000..d124eaa
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-listing2.py
@@ -0,0 +1,8 @@
+def successCallback(result):
+ myResult = result + 1
+ print myResult
+ return myResult
+
+...
+
+adder.callRemote("add", 1, 1).addCallback(successCallback)
diff --git a/doc/historic/2003/pycon/deferex/deferex-simple-failure.py b/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
new file mode 100644
index 0000000..34f6b29
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
@@ -0,0 +1,9 @@
+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/doc/historic/2003/pycon/deferex/deferex-simple-raise.py b/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
new file mode 100644
index 0000000..cd89b1a
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
@@ -0,0 +1,3 @@
+x = 1 + 3
+raise Exception("I can't go on!")
+print x
diff --git a/doc/historic/2003/pycon/deferex/deferex.html b/doc/historic/2003/pycon/deferex/deferex.html
new file mode 100644
index 0000000..a4fb171
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferex.html
@@ -0,0 +1,499 @@
+
+
+
+Generalization of Deferred Execution in Python
+
+
+
+Generalization of Deferred Execution in Python
+
+Glyph Lefkowitz
+
+
+
+Overview
+
+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.
+
+Other Popular Approaches
+
+There are several traditional approaches to handling concurrency that have
+been taken by application frameworks in the past. Each has its own
+drawbacks.
+
+Threads
+
+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.
+
+Callback Conventions
+
+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?
+
+Language Modifications
+
+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:
+
+
+ Erlang
+ Mozart/Oz
+ E
+ Scheme
+ Smalltalk
+
+
+ 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]
+
+Basics of Deferreds
+
+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.
+
+Fictitious Request Example
+
+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.
+
+Generalized Error Handling
+
+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.
+
+Reasonable Defaults
+
+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.
+
+Simple Catastrophic Deferred Failure
+
+In this example, the onSuccess callback will never be run, because the
+blowUp callback creates an error condition which is not handled.
+
+No Ambiguity about Failure
+
+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'
+
+Rich Information about Errors
+
+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.
+
+Complex Deferred Failure
+
+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.
+
+Easy Propagation of Context
+
+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.
+
+Patterns of Usage
+
+Since Deferred
is designed for a fairly specific class of problems, most
+places it is used tend to employ certain idioms.
+
+Request-ID Dictionary
+
+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.
+
+ Sometimes Synchronous Interface
+
+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.
+
+Forwarding Local and Remote
+Interfaces
+
+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.
+
+Advanced Features
+
+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.
+
+Chaining 2
+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.
+
+Conclusion
+
+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.
+
+Acknowledgements
+
+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..
+
+References
+
+
+
+ Marc Stiegler, The E Language in a
+ Walnut , erights.org
+
+ Jeremy Hylton, PEP 227, "Statically
+Nested Scopes"
+
+ Kent Pitman, UNWIND-PROTECT vs. Continuations , Kent Pitman's Personal FAQ
+
+ Christian Tismer, Continuations and Stackless
+ Python , Proceedings of the Sixth International Python Conference
+
+
+
+
+
+
+
diff --git a/doc/historic/2003/pycon/deferex/deferexex.py b/doc/historic/2003/pycon/deferex/deferexex.py
new file mode 100644
index 0000000..71c116e
--- /dev/null
+++ b/doc/historic/2003/pycon/deferex/deferexex.py
@@ -0,0 +1,16 @@
+
+# DEFERred EXecution EXamples
+
+### make sure errors come out in order
+import sys
+from twisted.python import log
+log.logerr = sys.stdout
+
+# Create a pseudo "remote" object for executing this stuff
+from twisted.spread.util import LocalAsRemote
+class Adder(LocalAsRemote):
+ def async_add(self, a, b):
+ print 'adding', a, b
+ return a + b
+
+adder = Adder()
diff --git a/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning b/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
new file mode 100644
index 0000000..d41d3af
--- /dev/null
+++ b/doc/historic/2003/pycon/intrinsics-lightning/intrinsics-lightning
@@ -0,0 +1,97 @@
+#!/usr/bin/python
+
+from slides import *
+from twslides import *
+
+class PythonSource:
+ def __init__(self, content):
+ self.content = content
+ def toHTML(self):
+ return '%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/doc/historic/2003/pycon/lore/lore-presentation b/doc/historic/2003/pycon/lore/lore-presentation
new file mode 100755
index 0000000..59a4d6a
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore-presentation
@@ -0,0 +1,108 @@
+#!/usr/bin/python2.2
+# Moshe -- current content is estimated at about 15 minutes
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+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("""\
+
+
+Example
+
+
+Example
+Simple paragraph
+
+
+""")),
+ Slide("Future Directions",
+ Bullet("More output formats"),
+ Bullet("Some more classes - abstract, bibliography"),
+ ),
+)
+
+lecture.renderHTML(".", "lore-%d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/lore/lore-slides.html b/doc/historic/2003/pycon/lore/lore-slides.html
new file mode 100755
index 0000000..19171f8
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore-slides.html
@@ -0,0 +1,187 @@
+
+ Lore: A Document Generation System
+
+
+ Lore: A Document Generation System
+
+ Andrew Bennetts <andrew@puzzling.org>
+ (Twisted Lore maintainer)
+
+ Introduction
+
+ Document generation system
+ Input format is essentially a subset of XHTML
+ Outputs nicely formatted HTML and LaTeX
+ Used to generate >200 pages of Twisted documentation
+
+
+ History
+
+ Twisted needed documentation -- and a format
+ We didn't want to depend on a big system
+
+ The lower the barrier for documentation contributions, the
+ better
+
+
+ We wanted something quick and easy
+
+ Lots of people already know simple HTML
+ People were already using HTML to write docs
+
+
+ Our needs matured: table of contents, printable version
+ So we created Lore
+
+
+ Goals
+
+ Easy to use for authors
+ Easy to install
+ (Uncommon) Source format should be readable
+
+
+
+
+ Small Example
+
+<html>
+<head>
+<title>Example</title>
+</head>
+<body>
+<h1>Example</h1>
+<p>Simple paragraph<span class="footnote">footnote</span></p>
+</body>
+</html>
+
+
+ Contents
+
+ twisted.lore Python package
+ lore
command-line program
+ Comes with every Twisted installation
+ In particular -- works on Linux, Win32, Mac
+ In particular -- supports Python 2.1, 2.2, 2.3 alpha
+
+
+ Alternatives - HTML
+
+ Too flexible
+ No support for needed idioms
+
+ Special-purpose Python markup
+ Tables of contents
+ Inlining
+
+
+ Renders badly to dead trees with current tools
+
+
+ Alternatives - LaTeX
+
+ Very good at printed results
+ LaTeX's design makes alternative parsers near-impossible
+ Renderers to HTML are buggy and fragile
+
+ Although the Python Standard Library seems to cope :-)
+
+
+
+
+ Alternatives - Docbook
+
+ Using correctly requires too much work
+
+ Write a DTD with special elements
+ Write Jade stylesheets
+
+
+ Lore is probably smaller than docbook specialization
+
+
+ Alternatives - Texinfo
+
+
+ Lore goodies
+
+ Special tag to mark classes/modules/functions
+
+ Can be made to point to auto-generated docs
+
+
+
+ Inline code-examples
+
+ No need to escape all those <, > and &
+
+
+
+ Syntax-highlight Python code
+
+
+ 'lore -o lint': A lint-like tool
+
+ Checks for many common errors
+
+ Invalid XML
+ Unhandled elements
+ Misspelled (or miscased) class names
+ Checks Python code for syntax errors
+
+
+
+
+ Extending Lore
+
+ Easily done with some Python code
+ Input-enhancements decide which output formats to handle
+ Example: math-lore, Lore with LaTeX formulae
+
+
+ Extending Lore (cont'd)
+
+
+ Another example: These slides!
+ The lore-slides
plugin can output to
+
+ Magicpoint
+ HTML (one page per slide)
+ HTML (one big page)
+
+
+
+
+ HTML Output
+
+ HTML is a flexible output format
+ Documents often have to integrate with a site
+ Lore produces HTML documents based on a template
+ Lore uses only HTML class
attributes, never font
+
+
+
+
+ Man Pages
+
+ Lore has a program to convert man pages to Lore documents
+ Man pages are written anyway
+ No man output: the format is too limited
+
+
+ Future Directions
+
+ More output formats
+ Some more classes -- abstract, bibliography
+ Index
+
+
+
+
+
diff --git a/doc/historic/2003/pycon/lore/lore.html b/doc/historic/2003/pycon/lore/lore.html
new file mode 100644
index 0000000..da71590
--- /dev/null
+++ b/doc/historic/2003/pycon/lore/lore.html
@@ -0,0 +1,791 @@
+
+
+
+
+
+
+The Lore Document Generation Framework
+
+
+
+
+The Lore Document Generation Framework
+
+
+
+Abstract
+
+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.
+
+History
+
+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.
+
+Introduction
+
+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.
+
+Goals
+
+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:
+
+
+Source files directly readable.
+At least output to modern (CSS-based) HTML.
+Easily parsed by third-parties.
+
+
+Source Format
+
+Description
+
+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.
+
+Advantages and Disadvantages
+
+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:
+
+
+There is no table of contents.
+Footnotes interrupt the flow of text (although stylesheet tricks can
+alleviate this to an extent).
+Python source is not syntax highlighted.
+File inclusions are implemented as hyper-links.
+
+
+Output Formats
+
+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
.
+
+HTML
+
+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>
.
+
+LaTeX
+
+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
+
+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.
+
+Lint
+
+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:
+
+
+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.
+
+Features
+
+Python Syntax Highlighting
+
+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
.
+
+File Inclusion
+
+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.
+
+API Reference Links
+
+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
+Deferred
. 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 title
+attribute to the API reference, containing the full name of the link.
+
+Cross references
+
+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 Support
+
+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.
+
+Comparisons
+
+HTML
+
+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.
+
+LaTeX
+
+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
+
+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
+
+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
+
+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.
+
+Techniques
+
+Visitor Pattern
+
+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.
+
+Liberal Use of Reflection
+
+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
.
+
+Recursively Searching For Elements
+
+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.
+
+Lessons Learned
+
+Problems With Some Output Formats
+
+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:
+
+
+Different characters are escaped differently (for example, \
+ is escaped, in TeX, as $\backslash$
while most other
+ characters are escaped as \<char>
.
+Escaping depends on context -- special characters should not be escaped
+ at all inside 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.
+
+Event-based XML Parsing Considered Harmful
+
+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.
+
+Allow Easy Modification
+
+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.
+
+Reinventing Wheels Can Be Useful
+
+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.
+
+Availability
+
+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.
+
+Future Plans
+
+More Output Formats
+
+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.
+
+Image Conversion
+
+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.
+
+Interface
+
+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.
+
+Twisted Integration
+
+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.
+
+Further Resources
+
+
+
+
diff --git a/doc/historic/2003/pycon/pb/pb-client1.py b/doc/historic/2003/pycon/pb/pb-client1.py
new file mode 100755
index 0000000..7814fb7
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-client1.py
@@ -0,0 +1,46 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+from twisted.internet import reactor
+
+class Client:
+ def connect(self):
+ deferred = pb.getObjectAt("localhost", 8800, 30)
+ deferred.addCallbacks(self.got_obj, self.err_obj)
+ # when the Deferred fires (i.e. when the connection is established and
+ # we receive a reference to the remote object), the 'got_obj' callback
+ # will be run
+
+ def got_obj(self, obj):
+ print "got object:", obj
+ self.server = obj
+ print "asking it to add"
+ def2 = self.server.callRemote("add", 1, 2)
+ def2.addCallbacks(self.add_done, self.err)
+ # this Deferred fires when the method call is complete
+
+ def err_obj(self, reason):
+ print "error getting object", reason
+ self.quit()
+
+ def add_done(self, result):
+ print "addition complete, result is", result
+ print "now trying subtract"
+ d = self.server.callRemote("subtract", 5, 12)
+ d.addCallbacks(self.sub_done, self.err)
+
+ def err(self, reason):
+ print "Error running remote method", reason
+ self.quit()
+
+ def sub_done(self, result):
+ print "subtraction result is", result
+ self.quit()
+
+ def quit(self):
+ print "shutting down"
+ reactor.stop()
+
+c = Client()
+c.connect()
+reactor.run()
diff --git a/doc/historic/2003/pycon/pb/pb-server1.py b/doc/historic/2003/pycon/pb/pb-server1.py
new file mode 100755
index 0000000..c0fb43f
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-server1.py
@@ -0,0 +1,16 @@
+#! /usr/bin/python
+
+from twisted.spread import pb
+import twisted.internet.app
+
+class ServerObject(pb.Root):
+ def remote_add(self, one, two):
+ answer = one + two
+ print "returning result:", answer
+ return answer
+ def remote_subtract(self, one, two):
+ return one - two
+
+app = twisted.internet.app.Application("server1")
+app.listenTCP(8800, pb.BrokerFactory(ServerObject()))
+app.run(save=0)
diff --git a/doc/historic/2003/pycon/pb/pb-slides.py b/doc/historic/2003/pycon/pb/pb-slides.py
new file mode 100755
index 0000000..1a9aea6
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb-slides.py
@@ -0,0 +1,240 @@
+#! /usr/bin/python
+
+from slides import Lecture, NumSlide, Slide, Bullet, SubBullet, PRE, URL
+
+class Raw:
+ def __init__(self, title, html):
+ self.title = title
+ self.html = html
+ def toHTML(self):
+ return self.html
+
+class HTML(Raw):
+ def __init__(self, html):
+ self.html = html
+
+server_lore = """
+
class ServerObject ( pb . Referenceable ) :
+ def remote_add ( self , one , two ) :
+ answer = one + two
+ print "returning result:" , answer
+ return answer
+
Server Code
+
+"""
+
+client_lore = """
+ def got_RemoteReference ( remoteref ) :
+ print "asking it to add"
+ deferred = remoteref . callRemote ( "add" , 1 , 2 )
+ deferred . addCallbacks ( add_done , err )
+ def add_done ( result ) :
+ print "addition complete, result is" , result
+ Client Code
+"""
+
+
+# title graphic: PB peanut butter jar, "Twist(ed)" on lid
+lecture = Lecture(
+ "Perspective Broker: Translucent RPC in Twisted",
+ # intro
+ Raw("Title", """
+ Perspective Broker: Translucent RPC in Twisted
+ PyCon 2003
+ Brian Warner < warner @ lothar . com >
+ """),
+
+ Slide("Introduction",
+ Bullet("Overview/definition of RPC"),
+ Bullet("What is Perspective Broker?"),
+ Bullet("How do I use it?"),
+ Bullet("Security Issues"),
+ Bullet("Future Directions"),
+ ),
+
+ Slide("Remote Procedure Calls",
+ Bullet("Action at a distance: separate processes, safely telling each other what to do",
+ SubBullet("Separate memory spaces"),
+ SubBullet("Usually on different machines"),
+ ),
+ Bullet("Frequently called RMI these days: Remote Method Invocation"),
+ Bullet("Three basic parts: Addressing, Serialization, Waiting"),
+ ),
+
+ Slide("Addressing",
+ Bullet("What program are you talking to?",
+ SubBullet("hostname, port number"),
+ SubBullet("Some systems use other namespaces: sunrpc")
+ ),
+ Bullet("Which object in that program?"),
+ Bullet("Which method do you want to run?"),
+ Bullet("Related issues",
+ SubBullet("How do you know what the arguments are?"),
+ SubBullet("(do you care?)"),
+ SubBullet("How do you know what methods are available?"),
+ SubBullet("(do you care?)"),
+ ),
+ ),
+
+ Slide("Serialization",
+ Bullet("What happens to the arguments you send in?"),
+ Bullet("What happens to the results that are returned?",
+ SubBullet("Representation differences: endianness, word length"),
+ SubBullet("Dealing with user-defined types"),
+ ),
+ Bullet("How to deal with references"),
+ ),
+ Slide("The Waiting (is the hardest part)",
+ Bullet("Asynchronous: results come later, or not at all"),
+ Bullet("Need to do other work while waiting"),
+ ),
+
+ Slide("Whither Translucence?",
+ Bullet("Not 'Transparent': don't pretend remote objects are really local",
+ SubBullet("CORBA (in C) does this, makes remote calls look like local calls"),
+ SubBullet("makes it hard to deal with the async nature of RPC"),
+ ),
+ Bullet("Not 'Opaque': make it easy to deal with the differences",
+ SubBullet("Including extra failure modes, delayed results"),
+ ),
+
+ Bullet("Exceptions and Deferreds to the rescue")),
+
+ Slide("Other RPC protocols",
+ Bullet("HTML"),
+ Bullet("XML-RPC"),
+ Bullet("CORBA"),
+ Bullet("when you control both ends of the wire, use PB"),
+ ),
+
+ Raw("Where does PB fit?",
+ """PB sits on top of twisted.internet
+
+ """),
+
+ Slide("pb.RemoteReference",
+ Bullet(HTML("pb.Referenceable : Object which can be accessed by remote systems."),
+ SubBullet(HTML("Defines methods like remote_foo and remote_bar which can be invoked remotely.")),
+ SubBullet(HTML("Methods without the remote_ prefix are local-only.")),
+ ),
+ Bullet(HTML("pb.RemoteReference : Used by distant program to invoke methods."),
+ SubBullet(HTML("Offers .callRemote() to trigger remote method on a corresponding pb.Referenceable .")),
+ ),
+ ),
+
+ Raw("Sample code",
+ "Sample Code " + server_lore + client_lore),
+ #Slide("Simple Demo"),
+ # "better demo: manhole, or reactor running in another thread"
+
+ #build up from callRemote?
+ Slide("What happens to those arguments?",
+ Bullet("Basic structures should travel transparently",
+ SubBullet("Actually quite difficult in some languages"),
+ ),
+ Bullet("Object graph should remain the same",
+ SubBullet("Serialization context"),
+ SubBullet("(same issues as Pickle)")),
+ Bullet("Instances of user-defined classes require more care",
+ SubBullet("User-controlled unjellying"),)
+ ),
+
+ #serialization (skip banana)
+ Slide("40% More Sandwich Puns Than The Leading Brand",
+ Bullet("twisted.spread: python package holding other modules"),
+ Bullet("PB: remote method invocation"),
+ Bullet("Jelly: mid-level object serialization"),
+ Bullet("Banana: low-level serialization of s-expressions"),
+ Bullet("Taster: security context, decides what may be received"),
+ Bullet("Marmalade: like Jelly, but involves XML, so it's bitter"),
+ Bullet("better than the competition",
+ SubBullet("CORBA: few or no sandwich puns"),
+ SubBullet("XML-RPC: barely pronounceable"),
+ ),
+ ),
+
+ Slide("Jellying objects",
+ Bullet("'Jellying' vs 'Unjellying'"),
+ Bullet("Immutable objects are copied whole"),
+ Bullet("Mutable objects get reference IDs to insure shared references remain shared",
+ SubBullet("(within the same Jellying context)")),
+ ),
+
+ Slide("Jellying instances",
+ Bullet(HTML("User classes inherit from one of the pb.flavor classes")),
+ Bullet(HTML("pb.Referenceable : methods can be called remotely")),
+ Bullet(HTML("pb.Copyable : contents are selectively copied")),
+ Bullet(HTML("pb.Cacheable : contents are copied and kept up to date")),
+ Bullet(HTML("Classes define .getStateToCopy and other methods to restrict exported state")),
+ ),
+
+ Slide("pb.Copyable example",
+ PRE("""class SenderPond(FrogPond, pb.Copyable):
+ def getStateToCopy(self):
+ d = self.__dict__.copy()
+ d['frogsAndToads'] = d['numFrogs'] + d['numToads']
+ del d['numFrogs']
+ del d['numToads']
+ return d
+
+class ReceiverPond(pb.RemoteCopy):
+ def setCopyableState(self, state):
+ self.__dict__ = state
+ self.localCount = 12
+ def count(self):
+ return self.frogsAndToads
+
+pb.setUnjellyableForClass(SenderPond, ReceiverPond)
+""")),
+
+ Slide("Secure Unjellying",
+ Bullet("Pickle has security problems",
+ SubBullet("Pickle will import any module the sender requests."),
+ SubBullet(HTML("2.3 gave up, removed safety checks like __safe_for_unpickling__ .")),
+ ),
+ Bullet("Jelly attempts to be safe in the face of hostile clients",
+ SubBullet("All classes rejected by default"),
+ SubBullet(HTML("registerUnjellyable() used to accept safe ones")),
+ SubBullet(HTML("Registered classes define .setCopyableState and others to process remote state")),
+ ),
+ Bullet("Must mark (by subclassing) to transmit"),
+ ),
+
+ Slide("Transformation of references in transit",
+ Bullet("All referenced objects get turned into their counterparts as they go over the wire"),
+ Bullet("References are followed recursively",
+ SubBullet("Sending a reference to a tree of objects will cause the whole thing to be transferred"),
+ SubBullet("(subject to security restrictions)"),
+ ),
+ Bullet(HTML("pb.flavors get reference ids"),
+ SubBullet("They are recognized when they return, transformed into the original reference"),
+ SubBullet("Reference ids are scoped to the connection"),
+ SubBullet("One side-effect: no 'third party' references"),
+ ),
+ ),
+
+ Slide("Perspectives: pb.cred and the Identity/Service model",
+ Bullet("A layer to provide common authentication services to Twisted applications"),
+ Bullet(HTML("Identity : named user accounts with passwords")),
+ Bullet(HTML("Service : something a user can request access to")),
+ Bullet(HTML("Perspective : user accessing a service")),
+ Bullet(HTML("pb.Perspective : first object, a pb.Referenceable used to access everything else")),
+ ),
+ #picture would help
+
+ Slide("Future directions",
+ Bullet("Other language bindings: Java, elisp, Haskell, Scheme, JavaScript, OCaml"),
+ # donovan is doing the JavaScript port
+ Bullet("Other transports: UDP, Airhook"),
+ Bullet("Componentization"),
+ Bullet("Performance improvements: C extension for Jelly"),
+ Bullet("Refactor addressing model: PB URLs"),
+ ),
+
+ Slide("Questions", Bullet("???")),
+
+ )
+
+lecture.renderHTML("slides", "slide-%02d.html", css="stylesheet.css")
+
diff --git a/doc/historic/2003/pycon/pb/pb.html b/doc/historic/2003/pycon/pb/pb.html
new file mode 100644
index 0000000..95f1ebe
--- /dev/null
+++ b/doc/historic/2003/pycon/pb/pb.html
@@ -0,0 +1,966 @@
+
+
+
+
+ Perspective Broker: Translucent Remote Method calls in Twisted
+
+
+
+
+Perspective Broker: Translucent Remote Method calls in Twisted
+
+
+
+Abstract
+
+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.
+
+Overview
+
+Features
+
+Perspective Broker provides the following basic RPC features.
+
+
+ remotely-invokable methods : certain methods (those
+ with names that start with remote_ ) of
+ pb.Referenceable
objects can be invoked by remote clients who
+ hold matching pb.RemoteReference
objects.
+
+ transparent, controllable object serialization : other
+ objects sent through those remote method invocations (either as arguments
+ or in the return value) will be automatically serialized. The data that is
+ serialized, and the way they are represented on the remote side, depends
+ upon which twisted.pb.flavor
class they inherit from, and
+ upon overridable methods to get and set state.
+
+ per-connection object ids : certain objects that are
+ passed by reference are tracked when they are sent over a wire. If the
+ receiver sends back the reference it received, the sender will see their
+ original object come back to them.
+
+ twisted.cred authentication layer : provides common
+ username/password verification functions. pb.Viewable
objects
+ keep a user reference with them, so remotely-invokable methods can find
+ out who invoked them.
+
+ remote exception reporting : exceptions that occur in
+ remote methods are wrapped in Failure
objects and serialized
+ so they can be provided to the caller. All the usual traceback information
+ is available on the invoking side.
+
+ runs over arbitrary byte-pipe transports : including
+ TCP, UNIX-domain sockets, and SSL connections. UDP support (in the form of
+ Airhook) is being developed.
+
+ numerous sandwich-related puns : PB, Jelly, Banana,
+ twisted.spread
, Marmalade, Tasters, and Flavors. By contrast,
+ CORBA and XML-RPC have few, if any, puns in their naming conventions.
+
+
+
+Example
+
+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 .
+
+Why Translucent References?
+
+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.
+
+Asynchronous Invocation
+
+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.
+
+Failure Modes
+
+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.
+
+Deferreds to the rescue
+
+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.
+
+
+Calling Remote Methods
+
+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
+bar
would invoke remote_bar
, etc.
+
+Obtaining other references
+
+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.
+
+Transferring Instances
+
+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
+
+
+
+pb.Copyable
+
+
+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).
+
+pb.Cacheable
+
+
+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
.
+
+
+Authorization
+
+
+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.
+
+Identity and Perspectives
+
+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.
+
+pb.Perspective
+
+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).
+
+Viewable
+
+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.
+
+
+
+
+PB Design: Object Serialization
+
+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.
+
+Banana and s-expressions
+
+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
+
+
+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.
+
+Tracking shared references
+
+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
+objects, 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.
+
+Representing Instances
+
+
+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 .
+
+Jellying Instances
+
+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.
+
+
+
+Secure Unjellying
+
+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.
+
+
+Broker
+
+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).
+
+PB over Jelly
+
+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.
+
+Per-Connection ID Numbers
+
+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.
+
+Reference Counting
+
+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.
+
+
+Security
+
+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.
+
+Controlled Object serialization
+
+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.
+
+Controlled Object Unserialization
+
+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.
+
+Controlled Method Invocation
+
+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
+remote_
prefix makes all remotely-invokable methods easy
+to locate, improving the focus of a security audit.
+
+Restricted Object Access
+
+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.
+
+Size Limits
+
+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.
+
+
+Future Directions
+
+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/doc/historic/2003/pycon/releasing/releasing-twisted b/doc/historic/2003/pycon/releasing/releasing-twisted
new file mode 100755
index 0000000..1b20155
--- /dev/null
+++ b/doc/historic/2003/pycon/releasing/releasing-twisted
@@ -0,0 +1,151 @@
+#!/usr/bin/python2.2
+# Moshe -- This seems like 30+ minutes to me!
+from slides import NumSlide, Slide, Bullet, SubBullet, PRE, URL
+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/doc/historic/2003/pycon/releasing/releasing.html b/doc/historic/2003/pycon/releasing/releasing.html
new file mode 100644
index 0000000..9e7bb61
--- /dev/null
+++ b/doc/historic/2003/pycon/releasing/releasing.html
@@ -0,0 +1,491 @@
+
+
+
+Managing the Release of a Large Python Project
+
+
+
+Managing the Release of a Large Python Project
+
+
+
+Abstract
+
+
+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.
+
+
+
+Introduction
+
+
+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.
+
+
+
+Overview of Steps
+
+
+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).
+
+
+
+
+ Test
+
+ Unit tests
+ Acceptance tests
+ Pre-release tests
+
+
+ Update the Changelog and README files
+ Run the release script
+
+ unix runs admin/release-twisted
+ Win32 runs win32/bdist_wininst.bat
+
+
+ Deploy: update twisted deployment on twistedmatrix.com
+ Upload to SourceForge mirror
+ Update Website
+
+
+
+
+Testing
+
+
+
+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.
+
+
+
+
+The release-twisted Script
+
+
+
+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
+
+
+
+
+Transactions
+
+
+
+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.
+
+
+
+
+CheckOut
+
+
+ checks out the latest revision of Twisted from CVS and puts it in
+ the Twisted CVS directory.
+
+
+
+UpdateVersion
+
+
+ changes the version number of the current release -- updating
+ twisted/copyright.py (the canonical location for the current
+ version) and a few other text files where the current version is
+ mentioned.
+
+
+
+
+Tag
+
+
+ tags the revisions in the current source tree with the version
+ passed in on the command line.
+
+
+
+
+Export
+
+
+
+ runs the cvs export command, which is similar to
+ checkout , but leaves out CVS support directories; this is
+ what we package up in the archives.
+
+
+
+
+PrepareDist
+
+
+ simply copies the directory containing the version of Twisted to be
+ released to a new directory specifically for the release
+ process. The reason that we have this extra copy is that sometimes
+ one will want to create a release from a directory that wasn't
+ created from the Export command; having the release script
+ munge that directory in-place would be impolite.
+
+
+
+
+GenerateDocs
+
+
+
+ generates the various documentation: HTML API documentation (via
+ Epydoc), HTML, PostScript, and PDF howto documentation (via
+ twisted.lore), and HTML man-pages (via lore, converted from the
+ nroff source).
+
+
+
+CreateTarballs
+
+
+ creates the various archives that each Twisted release involves:
+ tarred and gzipped or bzip2ed versions of archives with code plus
+ documentation, code without documentation, and only documentation.
+
+
+
+
+Release
+
+
+
+ copies all of the archives to a directory specified by the --release
+ parameter. This is meant to be a publically accessible directory,
+ thus the name Release .
+
+
+
+MakeDebs
+
+
+
+ creates the .deb packages and support files for the Twisted Debian
+ packages.
+
+
+
+InstallDebs
+
+
+
+ Creates an apt-gettable Debian package repository in the
+ (unfortunately hard-coded) /twisted/Debian directory.
+
+
+
+Sourceforge
+
+
+
+ uploads the archives and debian packages to Twisted's sourceforge
+ mirror at http://twisted.sourceforge.net/ .
+
+
+
+
+UpgradeDebian
+
+
+
+ Installs the recently-generated Debian packages via dpkg on
+ the local machine.
+
+
+
+
+
+
+setup.py
+
+
+
+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
.
+
+
+
+
+Building C extensions
+
+
+
+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.
+
+
+
+
+Installing scripts
+
+
+
+
+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)
+
+
+
+
+Windows Releases
+
+
+
+
+
+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
.
+
+
+
+
+
+Future Directions
+
+
+
+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.
+
+
+
+
+
+
diff --git a/doc/historic/2003/pycon/tw-deploy/tw-deploy b/doc/historic/2003/pycon/tw-deploy/tw-deploy
new file mode 100755
index 0000000..294bd73
--- /dev/null
+++ b/doc/historic/2003/pycon/tw-deploy/tw-deploy
@@ -0,0 +1,184 @@
+#!/usr/bin/python
+# Requires CVS Slides
+
+from slides import Lecture, Slide, TitleSlide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+
+PERL_PROCESSOR = """\
+from twisted.web import static, twcgi
+
+class PerlScript(twcgi.FilteredScript):
+ filter = '/usr/bin/perl' # Points to the perl parser
+"""
+
+RPY_EXAMPLE = """\
+from twisted.web import resource
+
+class MyGreatResource(resource.Resource):
+ def render(self, request):
+ return "foo"
+
+resource = MyGreatResource()
+"""
+
+lecture = Lecture(
+ "A Twisted Web Tutorial",
+
+ TitleSlide("Twisted Web -- A tutorial",
+ Image("twistedlogo.png"),
+ ),
+
+ Slide("Twisted Web -- Where does it fit?",
+ Image("twisted-overview.png"),
+ ),
+
+ Slide("Setup and Configuration Utilities",
+ Bullet("mktap"),
+ Bullet("twistd"),
+ Bullet("websetroot"),
+ ),
+
+ Slide("mktap",
+ Bullet("TAP Model"),
+ Bullet("General usage"),
+ Bullet("Flexibility and Power"),
+ ),
+
+ Slide("mktap web : Common Useful Options",
+ Bullet("--path"),
+ Bullet("--port"),
+ Bullet("--user"),
+ Bullet("--logfile"),
+ Bullet("--processor"),
+ ),
+
+ Slide("Sample mktap command lines",
+ Bullet(PRE("mktap web")),
+ Bullet(PRE("mktap web --path=/var/www --logfile=/var/log/twistedweb.log")),
+ Bullet(PRE("mktap web --port=80 --path=/var/www --user --mime-type=text/plain")),
+ Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlProcessor.PerlProcessor --index=index.pl")),
+ ),
+
+ Slide("twistd : An overview",
+ Bullet("Start a Twisted Application"),
+ Bullet("Loads and instance of twisted.internet.app.Application from a file"),
+ Bullet("Daemonizes, binds to appropriate ports, and starts the Twisted mainloop"),
+ ),
+
+ Slide("Sample twistd command lines",
+ Bullet(PRE("twistd -f web.tap -l /var/log/twisted.log")),
+ Bullet(PRE("twistd -f web.tap --pidfile /var/run/web.pid")),
+ ),
+
+ Slide("Shutting down twistd",
+ Bullet("On Unix (in general): "),
+ SubBullet(
+ Bullet(PRE("kill -9 `cat twistd.pid`"))),
+ Bullet("On Windows: "),
+ SubBullet(
+ Bullet("Cannot daemonize on Windows, so just run twistd in a command prompt"),
+ Bullet("Switch to the command prompt, and press Control-C"),
+ ),
+ ),
+
+ Slide("Shutdown TAPs",
+ Bullet("Since TAPs store persistent data for an application,\
+ a 'shutdown' TAP is created on twistd shutdown"),
+ Bullet("You'll often want to start your Twisted application\
+ on subsequent runs with the shutdown TAP"),
+ ),
+
+ Slide("twistd and security",
+ Bullet("When twistd is run as root, it will shed root privileges\
+ for the uid and gid of either the user that created the TAP or those\
+ specified on the mktap commandline."),
+ ),
+
+ # Try this out!
+ Slide("websetroot",
+ Bullet("Used to change what the root of the server points to"),
+ Bullet("Set it to a Resource contained either in a Python source file or a Pickle file"),
+ ),
+
+ Slide("Sample websetroot command lines",
+ Bullet(PRE("websetroot -p 80 -f web.tap --script rootResource.py")),
+ Bullet(PRE("websetroot -p 8080 -f web.tap --pickle rootPickle")),
+ ),
+
+ # Resource example
+ # Use the perl example from the docs
+ Slide("What's a Resource?",
+ Bullet("Everything in twisted in represented as twisted.web.resource.Resource object"),
+ Bullet("In general, two calls are made on a resource:"),
+ SubBullet(
+ Bullet(PRE("getChild()")),
+ Bullet(PRE("render()")),
+ ),
+ ),
+
+ Slide("Resource call examples:",
+ Bullet("/foo/bar/baz gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).render(request)")),
+ ),
+ Bullet("/foo/bar/baz/ gets converted to:"
+ ),
+ SubBullet(
+ Bullet(PRE("site.getChild('foo', request).getChild('bar', request).getChild('baz', request).getChild('', request).render(request)")),
+ ),
+ ),
+
+ Slide("What do Resources handle?",
+ Bullet("Out of the box, Twisted supports files of all types"),
+ Bullet("HTML, text, etc."),
+ Bullet("Default MIME type can be specified"),
+ ),
+
+ Slide("What about web development?",
+ Bullet("Twisted Web has what are called processors, which are instances\
+ of classes inherited from resource.Resource"),
+ Bullet("By default, Twisted supports the following file types:"),
+ SubBullet(
+ Bullet(".php"),
+ Bullet(".php3"),
+ Bullet(".cgi"),
+ Bullet(".epy"),
+ Bullet(".rpy"),
+ Bullet(".trp"),
+ ),
+ Bullet("You can also write your own"),
+ ),
+
+ Slide("Custom Processor: More than One Evil Way to Do It",
+ Bullet("A custom processor to handle Perl CGIs:"),
+ PRE(PERL_PROCESSOR),
+ Bullet("An example of how to use:"),
+ SubBullet(Bullet(PRE("mktap web --path=/home/nafai/public_html --processor=.pl=PerlScript.PerlScript")),
+ ),
+ ),
+
+ Slide("What about making my own resources?",
+ Bullet("Define a class that inherits from resource.Resource"),
+ Bullet("Define the render() method on that class"),
+ Bullet("For long requests, render() can return NOT_DONE_YET"),
+ Bullet("Then Create a .rpy file that sets resource = to an instance of the class"),
+ ),
+
+ Slide(".rpy example",
+ PRE(RPY_EXAMPLE),
+ ),
+
+ Slide("More Stuff",
+ Bullet("In other words, the slides I didn't get to write..."),
+ SubBullet(
+ Bullet("Distributed Servers"),
+ Bullet("Virtual Hosts"),
+ Bullet("Rewrite Rules"),
+ Bullet("Debian configuration"),
+ Bullet("twistedmatrix.com configuration"),
+ ),
+ ),
+)
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "tw_deploy-%02d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/tw-deploy/twisted-overview.png b/doc/historic/2003/pycon/tw-deploy/twisted-overview.png
new file mode 100644
index 0000000..6746a85
Binary files /dev/null and b/doc/historic/2003/pycon/tw-deploy/twisted-overview.png differ
diff --git a/doc/historic/2003/pycon/tw-deploy/twistedlogo.png b/doc/historic/2003/pycon/tw-deploy/twistedlogo.png
new file mode 100644
index 0000000..6226297
Binary files /dev/null and b/doc/historic/2003/pycon/tw-deploy/twistedlogo.png differ
diff --git a/doc/historic/2003/pycon/twisted-internet/twisted-internet.py b/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
new file mode 100644
index 0000000..1948d3e
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
@@ -0,0 +1,541 @@
+#!/usr/bin/python
+
+from slides import Lecture, Slide, Image, Bullet, PRE, URL, SubBullet, NumSlide, toHTML
+import os
+
+class Bad:
+ """Marks the text in red."""
+
+ def __init__(self, text):
+ self.text = text
+
+ def toHTML(self):
+ return '%s ' % toHTML(self.text)
+
+
+class Lecture(Lecture):
+
+ def getFooter(self):
+ return ''
+
+
+EVENT_LOOP_CODE = """\
+# pseudo-code reactor
+class Reactor:
+ def run(self):
+ while 1:
+ e = self.getNextEvent()
+ e.run()
+"""
+
+PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class Echo(Protocol):
+ def connectionMade(self):
+ print 'connection made with', self.transport.getPeer()
+ def dataReceived(self, data):
+ self.transport.write(data)
+ def connectionLost(self, reason):
+ print 'connection was lost, alas'
+"""
+
+SERVER_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class EchoFactory(ServerFactory):
+
+ def buildProtocol(self, addr):
+ p = Echo()
+ p.factory = self
+ return p
+"""
+
+RUNNING_SERVER_CODE = """\
+from twisted.internet import reactor
+
+f = EchoFactory()
+reactor.listenTCP(7771, f)
+reactor.run()
+"""
+
+CLIENT_PROTOCOL_CODE = """\
+from twisted.internet.protocol import Protocol
+
+class MyClientProtocol(Protocol):
+ buffer = ''
+ def connectionMade(self):
+ self.transport.write('hello world')
+ def dataReceived(self, data):
+ self.buffer += data
+ if self.buffer == 'hello world':
+ self.transport.loseConnection()
+"""
+
+CLIENT_FACTORY_CODE = """\
+from twisted.internet.protocol import ClientFactory
+
+class MyFactory(ClientFactory):
+
+ protocol = MyClientProtocol
+
+ def startedConnecting(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionMade(self, connector):
+ pass # we could connector.stopConnecting()
+ def clientConnectionLost(self, connector, reason):
+ connector.connect() # reconnect
+ def clientConnectionFailed(self, connector, reason):
+ print "connection failed"
+ reactor.stop()
+"""
+
+CLIENT_CONNECT_CODE = """\
+from twisted.internet import reactor
+
+reactor.connectTCP('localhost', 7771, MyFactory(), timeout=30)
+reactor.run()
+"""
+
+PULL_PRODUCER_CODE = """\
+class FileProducer:
+
+ def __init__(self, file, size, transport):
+ self.file = file; self.size = size
+ self.transport = transport # the consumer
+ transport.registerProducer(self, 0)
+
+ def resumeProducing(self):
+ if not self.transport: return
+ self.transport.write(self.file.read(16384))
+ if self.file.tell() == self.size:
+ self.transport.unregisterProducer()
+ self.transport = None
+
+ def pauseProducing(self): pass
+
+ def stopProducing(self):
+ self.file.close()
+ self.request = None
+"""
+
+PUSH_PRODUCER_CODE = """\
+from twisted.internet import reactor
+
+class GarbageProducer:
+
+ def __init__(self, transport):
+ self.paused = 0; self.stopped = 0
+ self.transport = transport
+ transport.registerProducer(self, 1)
+ self.produce()
+
+ def produce(self):
+ if not self.paused:
+ self.transport.write('blabla')
+ if not self.stopped:
+ reactor.callLater(0.1, self.produce)
+
+ def stopProducing(self):
+ self.stopped = 1
+
+ def pauseProducing(self):
+ self.paused = 1
+
+ def resumeProducing(self):
+ self.paused = 0
+"""
+
+SCHEDULING_CODE = """\
+from twisted.internet import reactor
+
+def f(x, y=1):
+ print x, y
+
+i = reactor.callLater(0.1, f, 2, y=4)
+i.delay(2)
+i.reset(1)
+i.cancel()
+"""
+
+FACTORY_START_CODE = """\
+from twisted.internet.protocol import ServerFactory
+
+class LogFactory(ServerFactory):
+
+ def startFactory(self):
+ self.log = open('log.txt', 'w')
+
+ def stopFactory(self):
+ self.log.close()
+"""
+
+LOGGING_CODE = """\
+from twisted.python import log
+
+# by default only errors are logged, to stderr
+logFile = open('log.txt', 'a')
+log.startLogging(logFile)
+
+log.msg('Something has occurred')
+"""
+
+LOGGING_ERRORS_CODE = """
+from twisted.python import log, failure
+
+e = ValueError('ONO')
+log.err(failure.Failure(e))
+
+try:
+ doSomethingElse()
+except:
+ log.deferr()
+"""
+
+SERVICE_CODE = """\
+from twisted.internet import app
+
+class FooService(app.ApplicationService):
+ def startService(self):
+ # do startup stuff
+ def stopService(self):
+ # do shutdown stuff
+ def foobrizate(self):
+ # business logic!
+
+application = app.Application('foobnator')
+svc = FooService('foo', application)
+application.getServiceNamed('foo') is svc # True
+"""
+
+RUNNABLE_APP_CODE = """\
+# this is web.py
+from twisted.internet import app
+from twisted.web import static, server
+
+application = app.Application('web')
+application.listenTCP(8080, server.Site(static.File('/var/www')))
+
+if __name__ == '__main__':
+ application.run(save=0)
+"""
+
+TWISTD_CODE = """\
+$ twistd -y web.py
+$ lynx http://localhost:8080
+$ kill `cat twistd.pid`
+"""
+
+GUI_CODE = """\
+from twisted.internet import gtkreactor
+gtkreactor.install()
+import gtk
+w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
+w.show_all()
+from twisted.internet import reactor
+reactor.run()
+"""
+
+lecture = Lecture(
+ "The twisted.internet Tutorial of Doom",
+
+ Slide("Part 1 - Introduction"),
+
+ # there are different ways to do networking
+ # mention processes are not cross-platform
+ Slide("Choosing a networking paradigm for the enterprise",
+ Bullet("Event driven"),
+ Bullet(Bad("Threads")),
+ Bullet("Others which we will ignore (processes, SEDA, ...)")),
+
+ # it's a metaphor!
+ Slide("Applied Bistromathics 101",
+ Bullet("Consider a restaurant as a network application"),
+ Bullet("Clients come in, make requests to the waiters"),
+ Bullet("Waiters act on clients' choices")),
+
+ # an event loop is efficient, doesn't waste time
+ # event loop is also used for GUIs
+ Slide("The event driven waiter",
+ Bullet("One waiter, serving all tables"),
+ Bullet("Waiter takes orders from tables to kitchen"),
+ Bullet("Waiter takes food from kitchen to tables")),
+
+ # not accurate, but the problems are real. avoid threads if you can
+ Slide("Threads (a caricature)",
+ Bullet(Bad("One waiter per table")),
+ SubBullet("Problems:",
+ Bullet(Bad("Expensive")),
+ Bullet(Bad("Waiters need to be careful not bump into each other")),
+ )),
+
+ # why threads are sometimes necessary
+ Slide("When do we want threads?",
+ Bullet("Long running, blocking operations"),
+ Bullet("Classic example: database access")),
+
+ # today we will discuss only (parts of) twisted.internet
+ Slide("Twisted: The Framework of Your Internet",
+ Image("twisted-overview.png")),
+
+ Slide("Project Stats",
+ Bullet("URL: ", URL("http://www.twistedmatrix.com")),
+ Bullet("License: LGPL"),
+ Bullet("Number of developers: approximately 20"),
+ Bullet("Version: 1.0.3"),
+ Bullet("Platforms: Unix, Win32"),
+ Bullet("Started in January 2000 by Glyph Lefkowitz")),
+
+ Slide("Part 2 - Basic Networking With Twisted"),
+
+ # quick review of how the internet works
+ Slide("Internet!",
+ Bullet("Network of interconnected machines"),
+ Bullet("Each machine has one (or more) IP addresses"),
+ Bullet("DNS maps names ('www.yahoo.com') to IPs (216.109.125.69)"),
+ Bullet("TCP runs on top of IP, servers listen on of of 65536 ports,"
+ " e.g. HTTP on port 80"),),
+
+ # we need to understand certain basic terms before we continue.
+ # the event loop is the last thing we run - it waits until
+ # an event occurs, then calls the appropriate handler.
+ Slide("Basic Definitions - Reactor",
+ Bullet("An object implementing the event loop",
+ PRE(EVENT_LOOP_CODE))),
+
+ Slide("Basic Definitions - Transport",
+ Bullet("Moves data from one location to another"),
+ Bullet("Main focus of talk are ordered, reliable byte stream transports"),
+ Bullet("Examples: TCP, SSL, Unix sockets"),
+ Bullet("UDP is a different kind of transport")),
+
+ # the client is the side which initiated the connection
+ # HTTP and SSH run on TCP-like transports, DNS runs on UDP or TCP
+ Slide("Basic Definitions - Protocol",
+ Bullet("Defines the rules for communication between two hosts"),
+ Bullet("Protocols communicate using a transport"),
+ Bullet("Typically there is a client, and server"),
+ Bullet("Examples: HTTP, SSH, DNS")),
+
+ Slide("All Together Now",
+ Bullet("The reactor gets events from the transports (read from network, write to network)"),
+ Bullet("The reactor passes events to protocol (connection lost, data received)"),
+ Bullet("The protocol tells the transport to do stuff (write data, lose connection)")),
+
+ # designing a new protocol is usually a bad idea, there are lots of
+ # things you can get wrong, both in design and in implementation
+ Slide("How To Implement A Protocol",
+ Bullet("Hopefully, you don't.")),
+
+ # XXX split into three expanded slides?
+ NumSlide("How To Not Implement A Protocol",
+ Bullet("Use an existing Twisted implementation of the protocol"),
+ Bullet("Use XML-RPC"),
+ Bullet("Use Perspective Broker, a remote object protocol")),
+
+ # connectionMade is called when connection is made
+ # dataReceived is called every time we receive data from the network
+ # connectionLost is called when the connection is lost
+ Slide("How To Really Implement A Protocol",
+ PRE(PROTOCOL_CODE)),
+
+ # factories - why?
+ Slide("Factories",
+ Bullet("A protocol instance only exists as long as the connection is there"),
+ Bullet("Protocols want to share state"),
+ Bullet("Solution: a factory object that creates protocol instances")),
+
+ # factory code - notice how protocol instances have access to the factory
+ # instance, for shared state. buildProtocol can return None if we don't
+ # want to accept connections from that address.
+ Slide("A Server Factory",
+ PRE(SERVER_CODE)),
+
+ # running the server we just wrote
+ Slide("Connecting A Factory To A TCP Port",
+ PRE(RUNNING_SERVER_CODE)),
+
+ # transport independence - using listenUNIX as example
+ Slide("Transport Independence",
+ Bullet("Notice how none of the protocol code was TCP specific"),
+ Bullet("We can reuse same protocol with different transports"),
+ Bullet("We could use listenUNIX for unix sockets with same code"),
+ Bullet("Likewise listenSSL for SSL or TLS")),
+
+ Slide("Client Side Protocol",
+ PRE(CLIENT_PROTOCOL_CODE)),
+
+ # client connections are different
+ Slide("Client Side Factories",
+ Bullet("Different requirements than server"),
+ Bullet("Failure to connect"),
+ Bullet("Automatic reconnecting"),
+ Bullet("Cancelling and timing out connections")),
+
+ # example client factory - explain use of default buildProtocol
+ Slide("Client Side Factories 2",
+ PRE(CLIENT_FACTORY_CODE)),
+
+ # connectTCP
+ Slide("Connection API",
+ PRE(CLIENT_CONNECT_CODE)),
+
+ # explain how transports buffer the output
+ Slide("Buffering",
+ Bullet("When we write to transport, data is buffered"),
+ Bullet("loseConnection will wait until all buffered data is sent, and producer (if any) is finished")),
+
+ # start/stopFactory
+ Slide("Factory Resources",
+ Bullet("Factories may want to create/clean up resources"),
+ Bullet("startFactory() - called on start of listening/connect"),
+ Bullet("stopFactory() - called on end of listening/connect"),
+ Bullet("Called once even if factory listening/connecting multiple ports")),
+
+ # example of restartable factory
+ Slide("Factory Resources 2",
+ PRE(FACTORY_START_CODE)),
+
+ Slide("Producers and Consumers",
+ Bullet("What if we want to send out lots of data?"),
+ Bullet("Can't write it out all at once"),
+ Bullet("We don't want to write too fast")),
+
+ Slide("Producers",
+ Bullet("Produce data for a consumer, in this case by calling transport's write()"),
+ Bullet("Pausable (should implement pauseProducing and resumeProducing methods)"),
+ Bullet("Push - keeps producing unless told to pause"),
+ Bullet("Pull - produces only when consumer tells it to")),
+
+ Slide("Consumers",
+ Bullet("registerProducer(producer, streaming)"),
+ Bullet("Will notify producer to pause if buffers are full")),
+
+ Slide("Sample Pull Producer",
+ PRE(PULL_PRODUCER_CODE)),
+
+ Slide("Sample Push Producer",
+ PRE(PUSH_PRODUCER_CODE)),
+
+ # scheduling events
+ Slide("Scheduling",
+ PRE(SCHEDULING_CODE)),
+
+ # pluggable reactors - why?
+ Slide("Choosing a Reactor - Why?",
+ Bullet("GUI toolkits have their own event loop"),
+ Bullet("Platform specific event loops")),
+
+ Slide("Choosing a Reactor",
+ Bullet("Twisted supports multiple reactors"),
+ Bullet("Default, gtk, gtk2, qt, win32 and others"),
+ Bullet("Tk and wxPython as non-reactors"),
+ Bullet("Reactor installation should be first thing code does")),
+
+ # example GUI client
+ Slide("Example GTK Program",
+ PRE(GUI_CODE)),
+
+ # you can learn more about
+ Slide("Learning more about networking and scheduling",
+ Bullet("twisted.internet.interfaces"),
+ Bullet("http://twistedmatrix.com/document/howtos/")),
+
+
+ Slide("Part 3 - Building Applications With Twisted"),
+
+ # the concept of the application
+ Slide("Applications",
+ Bullet("Reactor is a concept of event loop"),
+ Bullet("Application is higher-level"),
+ Bullet("Configuration, services, persistence"),
+ Bullet("Like reactor, you can listenTCP, connectTCP, etc.")),
+
+ # services concept
+ Slide("Services",
+ Bullet("Services can be registered with Application"),
+ Bullet("A service encapsulates 'business logic'"),
+ Bullet("Infrastructure outside the scope of protocols"),
+ Bullet("Examples: authentication, mail storage")),
+
+ # service example code
+ Slide("Services 2",
+ PRE(SERVICE_CODE)),
+
+ # logging
+ Slide("Logging",
+ PRE(LOGGING_CODE)),
+
+ # logging errors
+ # explain why this is good idea (twistd -b)
+ Slide("Logging Errors",
+ PRE(LOGGING_ERRORS_CODE)),
+
+ # twistd idea
+ Slide("twistd - Application Runner",
+ Bullet("Single access point for running applications"),
+ Bullet("Separate configuration from deployment")),
+
+ # twistd features
+ Slide("twistd Features",
+ Bullet("Daemonization"),
+ Bullet("Log file selection (including to syslog)"),
+ Bullet("Choosing reactor"),
+ Bullet("Running under debugger"),
+ Bullet("Profiling"),
+ Bullet("uid, gid"),
+ Bullet("Future: WinNT Services")),
+
+ # making modules for twistd -y
+ Slide("Making a runnable application",
+ PRE(RUNNABLE_APP_CODE)),
+
+ # running the server
+ Slide("Running twistd",
+ PRE(TWISTD_CODE)),
+
+ Slide("Part 4: Further Bits and Pieces"),
+
+ Slide("Other twisted.internet Features",
+ Bullet("UDP, Multicast, Unix sockets, Serial"),
+ Bullet("Thread integration")),
+
+ Slide("Deferreds",
+ Bullet("Deferred - a promise of a result"),
+ Bullet("Supports callback chains for results and exceptions"),
+ Bullet("Used across the whole framework"),
+ Bullet("Make event-driven programming much easier"),
+ Bullet("Can work with asyncore too, not just Twisted")),
+
+ Slide("Protocol implementations",
+ Bullet("Low-level implementations, without policies"),
+ Bullet("SSH, HTTP, SMTP, IRC, POP3, telnet, FTP, TOC, OSCAR, SOCKSv4, finger, DNS, NNTP, IMAP, LDAP"),
+ Bullet("Common GPS modem protocols")),
+
+ Slide("Frameworks",
+ Bullet("twisted.web - Web server framework"),
+ Bullet("twisted.news - NNTP server framework"),
+ Bullet("twisted.words - messaging framework"),
+ Bullet("twisted.names - DNS server")),
+
+ Slide("Perspective Broker",
+ Bullet("Object publishing protocol"),
+ Bullet("Fast, efficient and extendable"),
+ Bullet("Two-way, asynchronous"),
+ Bullet("Secure and encourages secure model"),
+ Bullet("Implemented in Python for Twisted, and Java")),
+
+ Slide("Lore",
+ Bullet("Simple documentation system"),
+ Bullet("Simple subset of XHTML"),
+ Bullet("Generates LaTeX, XHTML")),
+
+ Slide("Reality",
+ Bullet("Multiplayer text simulation framework"),
+ Bullet("Original source of Twisted project"),
+ Bullet("Now a totally different project")),
+)
+
+
+if __name__ == '__main__':
+ lecture.renderHTML(".", "twisted_internet-%02d.html", css="main.css")
diff --git a/doc/historic/2003/pycon/twisted-reality/componentized.svg b/doc/historic/2003/pycon/twisted-reality/componentized.svg
new file mode 100644
index 0000000..613192a
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-reality/componentized.svg
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Thing
+
+
+
+
+
+Weapon
+
+
+
+
+Portable
+
+
+
+(Componentized)
+
+
+
+
+(Adapter)
+
+
+
+(Adapter)
+
+
+
+
+(Adapter)
+
+
+
+Merchandise
+
+
+
diff --git a/doc/historic/2003/pycon/twisted-reality/twisted-reality.html b/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
new file mode 100644
index 0000000..bb96500
--- /dev/null
+++ b/doc/historic/2003/pycon/twisted-reality/twisted-reality.html
@@ -0,0 +1,578 @@
+
+
+
+
+
+ Twisted Reality: A Flexible Framework for Virtual Worlds
+
+
+
+ Twisted Reality: A Flexible Framework for Virtual Worlds
+
+
+
+ Abstract
+ 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.
+
+ Motivation
+
+ 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.
+
+
+The Twisted Component Model
+
+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.)
+
+Componentized
+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.
+
+Parsing System
+
+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: blow whistle
should obviously resolve to a
+single action, in this case 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 System
+
+
+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.
+
+
+Composing Simulations with Adapters
+
+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.
+
+Future Directions
+
+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.
+
+Conclusions
+
+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.
+
+Acknowledgements
+Thanks to Chris Armstrong and Donovan Preston for contributions to
+ Twisted Reality, and to Ying Li for editorial assistance.
+
+References
+
+Jason Asbahr, Beyond: A
+ Portable Virtual World Simulation Framework ,
+ Proceedings of the Seventh International Python
+ Conference (1998).
+
+Pavel Curtis, LambdaMOO programmer's manual , 1997.
+ Jim Fulton, Zope Component Architecture
+ Brandon Gillespie, ColdC Reference Manual , 2001.
+Glyph Lefkowitz, and Moshe Zadka, The Twisted Network Framework , Proceedings of the Tenth International Python Conference (2002): 83.
+Graham Nelson, The
+ Inform Designer's Manual . 4th ed. (St Charles, IL:
+ Interactive Fiction Library, 2001).
+
+
+
+
+
diff --git a/doc/historic/2004/ibm/talk.html b/doc/historic/2004/ibm/talk.html
new file mode 100644
index 0000000..56d1bad
--- /dev/null
+++ b/doc/historic/2004/ibm/talk.html
@@ -0,0 +1,495 @@
+Twisted: A Tutorial
+
+Twisted: A Tutorial
+
+Thanks
+
+I am grateful to IBM for inviting me to talk here, and to Muli Ben-Yehuda for arranging everything.
+
+Administrative Notes
+
+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!
+
+What Is Twisted?
+
+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
+
+An Extremely Short Introduction to Python
+
+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.
+
+How To Use Twisted
+
+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.
+
+How To Use Twisted to Do Nothing
+
+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.
+
+
+How To Use Twisted to Do Nothing (But Work Hard)
+
+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.
+
+How To Use Twisted to Be Rude
+
+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
.
+
+How To Use Twisted To Be Rude (In a Smart Way)
+
+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
.
+
+How To Use Twisted to Output Errors
+
+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.
+
+How to Use Twisted to Do Useful Things
+
+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.
+
+Using Twisted to Do Useful Things (For Real)
+
+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.
+
+Using Twisted to Do Useful Things, Correctly
+
+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.
+
+Using Twisted to Do The Standard Thing
+
+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.
+
+Use The Correct Port
+
+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...
+
+Running TAC Files
+
+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).
+
+Integrating Several Services
+
+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
+
+
Integrating Several Services: The Smart Way
+
+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.
+
+Periodic Tasks
+
+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?).
+
+Writing Clients: A Finger Proxy
+
+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)
+
+
+What I Did Not Cover
+
+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?
+
+
+Integration with GUI toolkits.
+Nevow, a web-framework.
+Twisted's internal remote call protocol, Perspective Broker.
+Trial, a unit testing framework optimized for testing Twisted-based
+ code.
+cred, the user management framework.
+Advanced deferred usage.
+Threads abstraction.
+Consumers/providers
+
+
+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.
+
+Notes on Non-Blocking
+
+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.
+
+
Conclusion
+
+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/doc/historic/FirstTenYears.Quotes b/doc/historic/FirstTenYears.Quotes
new file mode 100644
index 0000000..1c54867
--- /dev/null
+++ b/doc/historic/FirstTenYears.Quotes
@@ -0,0 +1,5816 @@
+December 5, 2000:
+Washort says, " $self._hasIntelligence()"
+Washort says, "1"
+Washort says, "*ponders setting that to 0 on certain people*"
+Maxwell says, "yes, that's our Ego-Enhancing API"
+ [this from before we had 'emote'. I added it 10 minutes later. -ed]
+%
+You say, "I wanted to discourage people from using the [old] code as examples...".
+You say, "but I don't think that bad java style is going to damage your budding programming skills :)".
+* washort nods. 'I seriously hope not.' [see, i told you I added 'emote' -ed]
+Washort says, "Oh, did i ever tell you about the Java assignment i had at the beginning of the semester?"
+Washort says, "I was bored so i did it without any loops or temp variables."
+Washort says, "so... don't tell ME about bad style. ;)"
+%
+ it's times like this when I wish I could just swallow my pride and use a standard thing like asyncore :)
+%
+ Since it's completely unsolicited, I'd just like to add that anyone who tries implementing Keynesian economics in this game will be put behind the door with the Elder Sign....
+%
+ I love Python. It has made me look smart in this consulting job. Because of how easily I was able to do what they need me to do, they ended up doubling my pay rate. :) Woot!
+ Woot *indeed*, good sir. :)
+%
+ you know, when I say *now* I mean "in a minute" :)
+%
+Glyph: "You need to start working on Twisted Reality."
+Mike: "What makes you say that?"
+Glyph: "Because it pains me to hear you talk about how you were 'in the same bed as' someone on AIM. There is no bed. There is no spoon. There's just some gay-ass peer-to-peer shit going on."
+%
+02:26:44 AreteComp: I've decided I'm going to warn you every 5 minutes until you go to bed.
+(You have been warned by AreteComp (5%))
+02:28:48 AreteComp: Tick, tick, motherfucker.
+%
+ "TONIGHT on CELEBRITY DEATHMATCH: Kenaan vs The Shrike"
+%
+ yow. autoconf can be *thorough* sometimes..
+ "checking for EBCDIC... no"
+ i hesitate to ask what it would have done had the answer been "yes"
+%
+ the http server was so we could say "Web!" if we ever did a freshmeat announcement
+ this makes people excited
+%
+FifthKow: jello is beyond good and evil
+%
+ besides, we need a way to handle the cases of characters on drugs...
+%
+ sorry glyph, but I have to take away your dork award. det is far more deserving.:)
+%
+ glyph: you be on tomorrow ?
+ det: what, you think I'll suddenly grow a life?
+%
+ det: if it were any more generic it would be socket.socket
+-- (responding to det's request to make twisted.web more generic)
+%
+ GenericBoy: Dude, this is *python*... objects get created when you sneeze
+%
+ I get the feeling that I could rack up some ad impressions by posting an announcement to FM about a webserver "powered entirely by love, that I made out of this bong I had".
+ Well, as long as it did something really l88t that other bong-servers didn't do, anyway.
+%
+01:35:08 AreteComp:
+Before you finish linking, you must answer the following:
+Are you a Jew?[y/N]: y
+Nice try, Yid.
+%
+ oh why do you mock me rpm
+%
+ oh no!
+%
+ http://www.twistedmatrix.com/whatisdivunal.html << makes it sound likes its done and played by millions worldwide
+ tpck: that's what ad copy is for
+%
+ glyph: I thought Enterprise Class Software wasn't supposed to crash?
+ tpck: It costs extra for the kind that doesn't crash, I think
+%
+ now you're probably wondering how to run cvs
+ actually i was thinking of naked women.
+ but sure.
+%
+<\\mimic> graydon: it's when you start constructing conditional branches in sed that the men in white coats come for you
+ mimic: been there, done that. wrote a qmail crypto extension in sed this summer :)
+%
+ dude tf programming, in my experience, was just about reading the help file and hacking something until it worked.
+ not really the kind of place to employ software engineering principles. ;)
+%
+ CanDoo: I would rather run a home trepaning centre than do tech support :)
+%
+ washort "A given program in PERL is like a turd. you can see it. smell it. touch it. yuck! it's definitely a turd. it's compact. it's smelly. it's brown. a turd, thru and thru"
+ washort "The *same* program in C/C++/Java/your favorite imperative language here is like a roll
+ of toilet paper, with the turd smeared *all over it*. you tear of one sheet. yuck! another sheet. ugh! another sheet. ewww! etc"
+ --- quoting Chet Murphy
+%
+ I get the feeling regexes in emacs are subtly different from python
+%
+ I'm not touching anything not abstracted from hardware at least two levels
+%
+ wh00t!
+ i made the quotefile!
+%
+ "lispachu, parentheses attack!"
+%
+ internet
+%
+ this was experimentally determined using an unholy combination of emacs, python's interactive mode, and bc
+%
+ perhaps i should write a "Teach yourself CL in 21 days" book and hide from Peter Norvig for a few years
+%
+ I'm not high!
+%
+ what is twisted python?
+ Mike_L: it's the python libraries your mother would use, if she were a programmer, had a lot of free time, and was very VERY patient
+%
+KaraNiSuru: Your opinion has differing degrees of importance to me. On
+programming, it's almost like law; on fashion, it's unimportant; on cuteness,
+it serves only to warn me away.
+(addressed to glyph, from his girlfriend)
+%
+ glyph: you're evil, too
+ washort: I try
+ not the good kind of evil
+ the other kind
+%
+ swing is to gui programming what cupholders are to cdrom drives
+ something easily mistaken for the real thing
+%
+ well, I'm working on divunal now
+ and what are you doing to it?
+ I'm making the clouds work again
+ the clouds were always one of my favorite bits
+ bah
+ typical vapourware
+%
+ washort: I learned C from reading the E sources.
+ glyph: well, i learned python from reading Zope
+ glyph: so i think we're about equally damaged
+%
+--> glyph (glyph@adsl-64-123-27-108.dsl.austtx.swbell.net) has joined #python
+ yay for pushing the wrong button
+ when will you xchat people learn
+ silly hacker, irc is for terminals
+ you dont see *me* typing '/quite' by accident ;)
+<-- washort has quit (either =))
+--> washort (washort@131.204.216.12) has joined #Python
+ glyph: you bastard.
+%
+ I wish I had enough knowledge to start working on this damn thing
+ glyph: But you had to crush my hopes. ;)
+ not that that's bad though, I am grateful for giving me a better
+perspective
+ GenericBoy: crushing hopes is what I do best
+ GenericBoy: you call me "glyph", but in ancient mesopotamia they called me the "eater of souls"
+%
+ many as-yet-untranslated pre-cuneform tablets will one day be
+translated to say "beware he who will write a webserver that will deprive you
+of your very will to live!"
+ GenericBoy: although I'm not sure if they were talking about me or
+marc andreissen
+%
+ I'll be the t.w guy from now on
+ yay!
+ YAY!
+ SOMEBODY ELSE IS GOING TO MAINTAIN MY SOFTWARE
+ oh god I think I'm going to cry
+ ack
+ GenericBoy: i think that was a mistake :)
+* GenericBoy runs
+%
+ GenericBoy: * New in 0.8.0: carmstro's soul now comes with twisted.web
+%
+ C:
+ char buf[1024]
+ strcpy(buf, user_data)
+ Python:
+ buf = user_data[:1024]
+ if len(user_data) > 1024: security_hole(user_data[1024:])
+ actually, the translation is not difficult, so long as you implement security_hole() properly.
+%
+ ... do u have an easier way
+ python ;-)
+ thank the lord
+ java is rediculous
+%
+ i can say with all confidence that my python code was the
+fastest and tightest code on the whole java project i been on for the last year
+%
+ OO is a seductive failure.
+%
+ washort: i don't want to take over the world
+ i want to marry the chick who is going to take over the world
+%
+ forth is much better than sanity
+%
+ Tim can go on at length on issues which are not really the core of the problem, complicating said problem for himself and everybody else.
+ glyph: sounds like you ;)
+ snibril: the difference is there is rarely actually a problem, when I'm involved :)
+%
+ So if I understand you correctly you want software that will b-2-b education portal internet enterprise mission-critical!
+ yep
+ Ah. then you want Zope.
+%
+ glyph: ok, where's the tw tutorial?
+ shapr: feh, you think there is *documentation*? You just need to be at harmony with the universe, and the api calls will come to you.
+ I just got a job writing Java. harmony is nowhere close to me.
+%
+ I just *love* your Python vs Java rant :) it's GREAT
+%
+ glyph: while reading through the last part of your rant, I got this mental picture of "Glyph Lefkowitz, Python Ninja" systematically chopping limbs off the JVM
+ glyph: the problem is that "don't expect your apps to run" was cutting the head off, and for cinematic effect, it should be on the bottom
+%
+ GenericBoy: but multiple eterms tailing various logfiles are great for making it look like your actually doing something :)
+ hehe
+ i'm preparing myself for when i have to work in a corporate setting
+%
+ I'm in the wrong channel.
+%
+ Yoso: i like to think that i'm a fairly sane individual for a python programmer anyway
+%
+ snibril: I think we should put *you* in the unit tests dir.
+%
+ have I mentioned there's a FRIGGIN BUTTLOAD of ways web input can go bad?
+%
+ GenericBoy: if we knew what we were doing, we would not call it programming
+%
+ Usually relying on magic buttons from the future doesn't work
+[in reply to something Mike_L said. --ed]
+%
+ hi glyph. i'm trying out python because of twisted python :)
+%
+ newpath = os.path.join(self.path, path)
+ # forgive me, oh lord, for I know not what I do
+ p, ext = os.path.splitext(newpath)
+%
+ There's a twisted python philosophy tutorial?
+ Nafai: yes.... read it, expand your consciousness
+ It's actually a new religion
+%
+ washort: coding angry lends whole new meaning to song lyrics :)
+%
+ what's the point of all of this?
+ GenericBoy: I don't know
+%
+ [ Read Past Entries ] [ Modify an entry ] [ Write new entry ] [ Have me add one for you. ]
+ include: [ ] generic angst [ ] relationship trouble (or lack thereof)
+ [ ] other family trouble [ ] cynical technology rants
+ also bash: [ ] slashdot [ ] users [ ] sysadmins [ ] politicians [ ] voters
+ [ ] Perl
+
+ -- proposed new configuration interface for the standard twisted.web weblog
+%
+ I'm going to write a treatise "girls as open-source projects".
+ Instead of "reaching second-base", you're "writing patches".
+ "So, are you writing patches for you-know-who?" "Well, no, but I'm using the CVS version"
+ Should translate to "We're only kissing, but that's as serious as it got"
+%
+ watching a beautiful girl sleep is amazingly fun
+ more fun than coding
+ do you mean 'more fun than coding Enterprise Applications in java', 'more fun than coding display hacks in C', or 'more fun than coding weblogs in python'?
+%
+ # ha ha, python can do lexical closures good enough for me
+ # (Bah. if these were lexical closures you wouldn't need the
+ # 'obj=obj', and you could do 'return setdesc' and the
+ # function would still work after escaping. -was)
+ def setdesc(desc, obj=obj):
+ obj.description=desc
+%
+<_Krelin> Data hiding and encapsulation are at least in-laws, if not blood brothers
+ data hiding is encapsulation's shrewish mother-in-law
+%
+ Zope is pretty much the reason i learned PHP, (and TPy is the reason i stopped)
+%
+* Nafai doesn't think he is worthy of the quotefile
+ you're in it twice
+%
+ "twisted python.... it's featurrific!"
+%
+ living is just syntactic sugar.
+%
+ glyph: what are you going to do now that UO2 is canceled ?
+ det: take over the world
+ det: same as before
+ washort: but thats what he was going to do last night
+ det: glyph is a man of habit
+%
+ who invented this "time zone" crap? everybody should be on IRC at once
+%
+--- washort has changed the topic to: | <-- you must be smarter than this stick to ride the internet
+%
+ TwistedPython may, in fact, have both "enterprise" AND "internet" ;)
+%
+ nothing like a pop tart to remind me i live in a first world country
+%
+ yosomono: in fact, I'll turn this box of Cheese Nips and
+ this 3-liter bottle of Mountain Dew into a irc2web interface
+%
+ actually i have clothes on
+ believe it or not
+%
+<\broken:#openprojects> geez that tomg bot is in here again
+<\broken:#openprojects> didn't we ban it a couple of times already
+%
+ uh, move zig zamboni to push grandma cats down the stairs to protect her/him from the terrible secret of space, which is that she/he can't skate?
+%
+ o/` once i was the king of spain o/`
+* Acapnotic throws a humble pie at washort
+%
+ the more I get into the art of design, the more I design things like I'm seven years old
+ 'I don't want to do things that way because it's too hard'
+ 'I wanna do it like this because I understand it'
+ 'I'm ignoring that because it's scary'
+ 'I don't want to work with him because he's a poopy-head'
+ 'I don't want to use this because it smells like poo'
+ 'this is no fun any more, I'm going home'
+%
+ Greetings, O Twisted One
+%
+ Someone please tell me that this thing about P3K and Perl 6 is just a sick April Fool's joke
+ Forest: what, print>> wasn't a big enough hint?
+%
+* moshez lives to workaround design decisions made by others.
+(-- after just proposing to implement IRC over HTTP via Zope.)
+%
+ /msg ry a/s/l
+%
+ okay, cvs is scaring me
+ glyph: when I was 5, when the other children were going as ninjas and dracula, I went as CVS!
+ det: you should have gone as SCCS
+ glyph: you gotta be a little cute to get the candy
+%
+ If you are anal, and you love to be right all the time, C++ gives you a multitude of mostly untimportant details to fret about so you can feel good about yourself for getting them "right", while missing the big picture entirely
+%
+ C++ extends the machine-efficiency requirement all the way up from
+ line-by-line implementation into entity abstraction as classes, it
+ corrupts far end of the coding spectrum with "efficiency" concerns.
+%
+ that's why I love IRC
+ you can't be late for IRC
+%
+ uh oh.
+ 'destroy here' isn't a good idea. :)
+%
+ glyph: the problem with writing a framework for text universe is
+that text adventure authors want to do the craziest shit :)
+%
+ are you jewish?
+ yes.
+ be afraid
+%
+ btw, e, what are the girls like in .fi?
+ bipedal, warm blooded, pink skinned, about 1.5-2.0 meters tall
+%
+ jeffk isn't funny, the people who think he's real are funny. :)
+ he isn't real?!?
+%
+ it'd be so cool. i'd feel all l33t and shit
+%
+ if they had neural interfaces to computers, we'd both be dead by now
+%
+ I declare myself god
+ the end
+%
+ GenericBoy is no more
+ I killed him, and have taken his place
+ radix: whadja do with the body?
+ Acapnotic: killed in a metophorical sense
+ that's what you think.
+ What happened to the metaphorical body?
+ Acapnotic: the metaphorical body is decaying at the bottom of lake washington
+ that's what he thinks.
+%
+ i'm convinced the core of loop [the Common Lisp facility] is a n-dimensional singularity and
+ that the common macro people implement is merrely the tessaract to loop's hypercube.
+%
+ I'm an at least somewhat-educated dope fiend
+%
+ I figured your lasers would be a good impetus to action.
+ Don't forget about them.
+ They're hovering, just over your head... where you can't see them. Remember that.
+ Okay.
+ Hm. That could be a cool theme for a new breakfast cereal!
+%
+ glyph: I don't know anything about reality.
+%
+ There are *many* differences between Texas and yogurt. Texas is drier than yogurt. Texas is larger than any amount of yogurt I've seen in one place at a time. (or ALL the yogurt I've seen at ANY time). Eating Texas would be less enjoyable than eating yogurt.
+ Texas does not come in eight ounce plastic containers with tinfoil lids. To the best of my knowledge, there is no "fruit on the bottom" version of Texas. Texas is not available in the dairy section of your grocer. Texas does not help fufill your daily dietary requirement of calcium.
+%
+ MAKE YOUR LOGO AL GORE ON A STICK
+%
+ radix: the question is, do you _really_ want to do that? :)
+ no, but I want to make other people do it
+%
+ i ascended several times.. once as a tourist without wishes or material transformations
+%
+ dash: Hey, what do you think a good visual aid for a talk on anarcho-capitalism would be?
+ radix: a gun.
+ radix: correction: a gun and a big pile of money :)
+%
+ Most large software projects are disasters. Nothing new there.
+ most large software projects use java or C++. not a coincidence.
+%
+ the program isn't debugged until the last user is dead
+%
+ glyph: I prefer to think of it as a community project...since not every interface is equal
+ some interfaces are more equal then others.
+%
+ glyph: why not use xml? (only because it is sort of a python standard [dont kill me])
+%
+ can you do socket programming with python ?
+ boy can you _ever_
+%
+ In python, you can, but in Java you can't.
+ [ this comment had context, but it's really just axiomatic --ed]
+%
+ I'm not a python luminary, I just play one on TV. :)
+%
+ but the point is, i dont have to juggle dlopen() bullshit
+ because that gets old real fast
+%
+ shapr-werk: I can't even imagine the hell of having to write java while quitting smoking. I am behind you 100% ;)
+ glyph: yah, anyone in front of me has already been mauled :-)
+%
+ glyph: You have created a powerful solution for which there are no problems. Everyone is impressed, but duly confused.
+%
+ crack! *that's* what I need!
+%
+ I like writing code that overloads operators in python
+ get help
+%
+ rasterman is the millionth monkey
+%
+ john tesh get out of my head!
+%
+ i want distributed everything
+ yesterday
+%
+ glyph please please dont jump on the P2P XML bandwagon
+ parks: satan will be buying ice skates before glyph does that
+%
+ this is where I tell you to stop hyperfocussing on bad stuff and think about something nifty like metaclasses or sex
+%
+* itamar loves changing an object's own class at run time
+ itamar: and you eat little babies, too
+%
+ So glyph is a master of the occult as well as the obscure. :)
+%
+ "What?" "Take the red continuation." "What?" "Take the blue continuation." "Huh?" "Take the red continuation." "What?"...
+%
+* rik cheers for twisted python
+ it's the easiest network coding toolkit I've come across
+ as soon as you have the flash of inspiration as to how it works, you'll not look back
+%
+ ugh. linear cosmologist fever.
+%
+ we have powers that reach beyond the pickle
+%
+ "No one expects the python acquisition!"
+%
+ I'm sorry. I used to be sane. Then I learned Perl and now I'm like this. ;)
+ laotse: that's my excuse too
+ laotse: that, and 4 years of university CS
+%
+ i get my best programming done in the nude
+%
+ what is the recommended way to do client sockets in python
+ timmy: a chainsaw! AHAHAHAHA!
+* e2d2@ircnet goes back to sleep
+%
+ No more doc about twist-dee, needs another page or three...
+ You call this an application server? This is a slide projector and a bedsheet!
+* Acapnotic is going to have to speak to Bob about this.
+%
+ dash: that's the cool part of system programming, programming half-finished programs, and tell others you're finished
+%
+