Plan/BetterExamples

Version 7 (modified by rwall, 13 months ago)

A list of example scripts

Better Examples

This plan is written as part of ticket:84.

It contains ideas for improving the quality, maintainability and testing of Twisted example code.

The following ideas came from the comments in #84 and from some discussions in #twisted-dev.

Once these ideas have been reviewed, they will be moved to separate tickets where more detailed discussion can take place and then #84 can be closed.

Tickets to be raised in one of:

Review and update writing and coding standards for example scripts

 The example writing standard

This document has good guidance, but it also raises some questions for consideration:

  1. "example code should be a complete working example suitable for copying and pasting and running by the reader"
    • Should it also be cross platform? Or should there be platform specic examples? eg can an example refer to /etc/hosts
  2. "example code should be short"
    • How short? In terms of lines? Or in terms of the number of top level fuctions and classes? Perhaps an example of a too long example.
  3. "example code should be commented very extensively"
    • Does this include docstrings?
    • Does this include epytext style API docs in docstrings?
    • Can too many comments be distracting in a simple example?
  4. "example code should conform to the coding standard"
    • Yes. Although having three lines between top level items makes the examples longer (in terms of line count).
  5. "example code should exhibit 'best practice'"
    • Should we identify and remove examples which refer to old deprecated APIs?
    • What is best practice? I guess most Twisted core devs will agree on most things, but are there any controversial practices eg inlineCallbacks?
    • Can examples ever use, or suggest using external libraries? Eg treq, Nevow, ampoule, tx??? etc. If those external libraries are considered best practice? I *think* its quite common for people to be directed to better external alternatives eg treq vs Agent or wokel?? vs twisted.words??
    • Should we refer / link to good external examples which demonstrate best practice?

 The example coding standard

Re examples, this document currently only refers to the requirement for #!/usr/bin/env python

Depending on the answers to questions above, it may need expanding. eg

  1. Do examples need full epytext documentation?
    • No. The example API is not going to be published
    • No. The epytext markup is a distraction from the example code.
    • Yes. It demonstrates Twisted best practice to new users.

This ticket can be closed when the writing and coding standards have been reviewed, and if necessary modified to clearly define how examples should be written in future.

Find / create tickets for fixing broken or bad examples

See List of Example Scripts

Review each of the examples and decide whether to maintain or remove it.

Look for examples which:

  • don't work
  • demonstrate deprecated APIs.
  • are redundant eg doc/web/examples/getpage.py seems to overlap doc/web/examples/dlpage.py
  • contain errors
  • do not adhere to coding standards
  • do not follow best practice

Then:

  1. Create a ticket for the example
  2. Summarise the state of the example in the ticket.
  3. Choose its fate: "maintain" or "delete".
  4. Enter the ticket number and action against the example in the example checklist in wiki:Plan/BetterExamples

Pyflakes builder should check example code

Twistedchecker should check example code

ExampleTestBase class for unit testing example code

A mechanism for running functional tests on example code

Tom Prince (and others) suggest that there should be functional tests for the examples.

twistedchecker already has functional tests so we can build something inspired by or based on that system:

    def test_functions(self):
        """
        This will automatically test some functional test files
        controlled by C{RunnerTestCase.configFunctionalTest}.
        """
        print >> sys.stderr, "\n\t----------------"
        testmodules = self.listAllTestModules()
        for pathTestFile, modulename in testmodules:
            pathResultFile = pathTestFile.replace(".py", ".result")
            self.assertTrue(os.path.exists(pathTestFile),
                       msg="could not find testfile:\n%s" % pathTestFile)
            self.assertTrue(os.path.exists(pathResultFile),
                       msg="could not find resultfile:\n%s" % pathResultFile)
            self.clearOutputStream()
            runner = Runner()
            runner.allowOptions = False
            runner.setOutput(self.outputStream)
            # set the reporter to C{twistedchecker.reporters.test.TestReporter}
            runner.setReporter(TestReporter())
            self._limitMessages(pathTestFile, runner)
            # enable pep8 checking
            pep8Checker = self._getChecker(runner, "pep8")
            if pep8Checker:
                pep8Checker.pep8Enabled = True
            runner.run([modulename])
            # check the results
            if self.debug:
                print >> sys.stderr, self.outputStream.getvalue()
            predictResult = self._removeSpaces(open(pathResultFile).read())
            outputResult = self._removeSpaces(self.outputStream.getvalue())
            self.assertEqual(outputResult, predictResult,
                 "Incorrect result of %s, should be:\n---\n%s\n---" % \
                 (modulename, predictResult))
            print >> sys.stderr, "\t%s\n" % modulename
        print >> sys.stderr, "\t----------------\n"

Rather than duplicate that code, perhaps we should take the opportunity to create a general FunctionalTestBase class which can then be re-used by twistedchecker.

Put it in eg in:

Example command line arguments and sample output would be placed in files located near the examples.

Perhaps in a test directory prefixed with an underscore so that they don't interfere with command line completion of the actual example eg

  • docs/names/examples/dns-service.py
  • docs/names/examples/_test_dns-service
  • docs/names/examples/_test_dns-service/gmail_com.arguments
    xmpp-client tcp gmail.com
    
  • docs/names/examples/_test_dns-service/gmail_com.stdout
    _xmpp-client._tcp.gmail.com IN
     <SRV priority=20 weight=0 target=alt2.xmpp.l.google.com port=5222 ttl=897>
     <SRV priority=20 weight=0 target=alt1.xmpp.l.google.com port=5222 ttl=897>
     <SRV priority=20 weight=0 target=alt3.xmpp.l.google.com port=5222 ttl=897>
     <SRV priority=20 weight=0 target=alt4.xmpp.l.google.com port=5222 ttl=897>
     <SRV priority=5 weight=0 target=xmpp.l.google.com port=5222 ttl=897>
    
  • docs/names/examples/_test_dns-service/non_existent_subdomain_example_com.arguments
    xmpp-client tcp non.existent.subdomain.example.com
    
  • docs/names/examples/test_dns-service/non_existent_subdomain_example_com.stderr
    ERROR: domain name not found '_xmpp-client._tcp.non.existent.subdomain.example.com'
    

Should the functional tests use the network or access real files?

  1. No. It will make the tests non-deterministic.
  1. No. It will make the functional tests slow.
  1. Yes. It will give some assurance that the example will work for the enduser when it's called with the arguments suggested in our docs.

eg. The DNS example scripts each contain - in their __main__.__doc__ - an example of how to call the script using a real world domain name.

By testing with real network, we will be alerted if that domain name ever changes or if the returned DNS records ever change. At which point we can update the example or the test.

List of Example Scripts

[richard@zorin trunk]$ find doc -type f -iname '*.py' -or -iname '*.tac'| while read line; do echo "|| || source:trunk/${line} ||"; done
source:trunk/doc/pair/examples/pairudp.py
source:trunk/doc/core/benchmarks/failure.py
source:trunk/doc/core/benchmarks/netstringreceiver.py
source:trunk/doc/core/benchmarks/linereceiver.py
source:trunk/doc/core/benchmarks/timer.py
source:trunk/doc/core/benchmarks/tpclient.py
source:trunk/doc/core/benchmarks/tpclient_nt.py
source:trunk/doc/core/benchmarks/task.py
source:trunk/doc/core/benchmarks/tpserver.py
source:trunk/doc/core/benchmarks/deferreds.py
source:trunk/doc/core/benchmarks/tpserver_nt.py
source:trunk/doc/core/benchmarks/banana.py
source:trunk/doc/core/development/listings/new_module_template.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger07.py
source:trunk/doc/core/howto/tutorial/listings/finger/twisted/plugins/finger_tutorial.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger17.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger08.py
source:trunk/doc/core/howto/tutorial/listings/finger/organized-finger.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger19.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger05.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger_config.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger06.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger15.tac
source:trunk/doc/core/howto/tutorial/listings/finger/simple-finger.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger02.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger19a_changes.py
source:trunk/doc/core/howto/tutorial/listings/finger/fingerPBclient.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger19b.tac
source:trunk/doc/core/howto/tutorial/listings/finger/fingerXRclient.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger04.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger19c.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger12.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger11.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger16.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger10.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger01.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger09.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger19a.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger21.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger22.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger13.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger19c_changes.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger03.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger14.tac
source:trunk/doc/core/howto/tutorial/listings/finger/fingerproxy.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger/__init__.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger/finger.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger/tap.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger19b_changes.py
source:trunk/doc/core/howto/tutorial/listings/finger/finger20.tac
source:trunk/doc/core/howto/tutorial/listings/finger/finger18.tac
source:trunk/doc/core/howto/listings/TwistedQuotes/__init__.py
source:trunk/doc/core/howto/listings/TwistedQuotes/quotetap2.py
source:trunk/doc/core/howto/listings/TwistedQuotes/quoteproto.py
source:trunk/doc/core/howto/listings/TwistedQuotes/pbquoteclient.py
source:trunk/doc/core/howto/listings/TwistedQuotes/quotetap.py
source:trunk/doc/core/howto/listings/TwistedQuotes/quoters.py
source:trunk/doc/core/howto/listings/TwistedQuotes/pbquote.py
source:trunk/doc/core/howto/listings/application/service.tac
source:trunk/doc/core/howto/listings/pb/pb1client.py
source:trunk/doc/core/howto/listings/pb/cache_sender.py
source:trunk/doc/core/howto/listings/pb/pb1server.py
source:trunk/doc/core/howto/listings/pb/trap_server.py
source:trunk/doc/core/howto/listings/pb/pb5client.py
source:trunk/doc/core/howto/listings/pb/exc_client.py
source:trunk/doc/core/howto/listings/pb/cache_receiver.py
source:trunk/doc/core/howto/listings/pb/pb2client.py
source:trunk/doc/core/howto/listings/pb/copy2_sender.py
source:trunk/doc/core/howto/listings/pb/copy_sender.py
source:trunk/doc/core/howto/listings/pb/pbAnonServer.py
source:trunk/doc/core/howto/listings/pb/pb6server.py
source:trunk/doc/core/howto/listings/pb/trap_client.py
source:trunk/doc/core/howto/listings/pb/pb6client1.py
source:trunk/doc/core/howto/listings/pb/exc_server.py
source:trunk/doc/core/howto/listings/pb/chatserver.py
source:trunk/doc/core/howto/listings/pb/cache_classes.py
source:trunk/doc/core/howto/listings/pb/pb6client2.py
source:trunk/doc/core/howto/listings/pb/pb3client.py
source:trunk/doc/core/howto/listings/pb/pb5server.py
source:trunk/doc/core/howto/listings/pb/pb3server.py
source:trunk/doc/core/howto/listings/pb/pb4client.py
source:trunk/doc/core/howto/listings/pb/copy2_classes.py
source:trunk/doc/core/howto/listings/pb/pb7client.py
source:trunk/doc/core/howto/listings/pb/copy_receiver.tac
source:trunk/doc/core/howto/listings/pb/pbAnonClient.py
source:trunk/doc/core/howto/listings/pb/copy2_receiver.py
source:trunk/doc/core/howto/listings/pb/pb2server.py
source:trunk/doc/core/howto/listings/pb/chatclient.py
source:trunk/doc/core/howto/listings/servers/chat.py
source:trunk/doc/core/howto/listings/sendmsg/send_replacement.py
source:trunk/doc/core/howto/listings/sendmsg/copy_descriptor.py
source:trunk/doc/core/howto/listings/trial/calculus/__init__.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_client_1.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_base_3.py
source:trunk/doc/core/howto/listings/trial/calculus/test/__init__.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_base_2.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_remote_2.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_remote_1.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_base_2b.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_client_3.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_client_4.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_client_2.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_base_1.py
source:trunk/doc/core/howto/listings/trial/calculus/test/test_remote_3.py
source:trunk/doc/core/howto/listings/trial/calculus/base_1.py
source:trunk/doc/core/howto/listings/trial/calculus/remote_2.py
source:trunk/doc/core/howto/listings/trial/calculus/remote_1.py
source:trunk/doc/core/howto/listings/trial/calculus/client_2.py
source:trunk/doc/core/howto/listings/trial/calculus/client_3.py
source:trunk/doc/core/howto/listings/trial/calculus/client_1.py
source:trunk/doc/core/howto/listings/trial/calculus/base_2.py
source:trunk/doc/core/howto/listings/trial/calculus/base_3.py
source:trunk/doc/core/howto/listings/udp/MulticastClient.py
source:trunk/doc/core/howto/listings/udp/MulticastServer.py
source:trunk/doc/core/howto/listings/cred/pop3_server.py
source:trunk/doc/core/howto/listings/process/quotes.py
source:trunk/doc/core/howto/listings/process/process.py
source:trunk/doc/core/howto/listings/process/trueandfalse.py
source:trunk/doc/core/howto/listings/deferred/synch-validation.py
source:trunk/doc/core/howto/listings/amp/basic_server.tac
source:trunk/doc/core/howto/listings/amp/command_client.py
source:trunk/doc/core/howto/listings/amp/basic_client.py
source:trunk/doc/core/examples/pbechoclient.py
source:trunk/doc/core/examples/echoclient_udp.py
source:trunk/doc/core/examples/pb_exceptions.py
source:trunk/doc/core/examples/shaper.py
source:trunk/doc/core/examples/recvfd.py
source:trunk/doc/core/examples/dbcred.py
source:trunk/doc/core/examples/rotatinglog.py
source:trunk/doc/core/examples/longex.py
source:trunk/doc/core/examples/tkinterdemo.py
source:trunk/doc/core/examples/gpsfix.py
source:trunk/doc/core/examples/ampclient.py
source:trunk/doc/core/examples/echoserv_ssl.py
source:trunk/doc/core/examples/ptyserv.py
source:trunk/doc/core/examples/testlogging.py
source:trunk/doc/core/examples/pbbenchclient.py
source:trunk/doc/core/examples/ampserver.py
source:trunk/doc/core/examples/pbinterop.py
source:trunk/doc/core/examples/pbgtk2.py
source:trunk/doc/core/examples/stdiodemo.py
source:trunk/doc/core/examples/wxdemo.py
source:trunk/doc/core/examples/pbbenchserver.py
source:trunk/doc/core/examples/filewatch.py
source:trunk/doc/core/examples/bananabench.py
source:trunk/doc/core/examples/pbsimple.py
source:trunk/doc/core/examples/simpleclient.py
source:trunk/doc/core/examples/simpleserv.py
source:trunk/doc/core/examples/shoutcast.py
source:trunk/doc/core/examples/postfix.py
source:trunk/doc/core/examples/simple.tac
source:trunk/doc/core/examples/mouse.py
source:trunk/doc/core/examples/ftpserver.py
source:trunk/doc/core/examples/threadedselect/pygamedemo.py
source:trunk/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/setup.py
source:trunk/doc/core/examples/threadedselect/Cocoa/SimpleWebClient/Twistzilla.py
source:trunk/doc/core/examples/threadedselect/blockingdemo.py
source:trunk/doc/core/examples/longex2.py
source:trunk/doc/core/examples/twistd-logging.tac
source:trunk/doc/core/examples/echoserv.py
source:trunk/doc/core/examples/chatserver.py
source:trunk/doc/core/examples/streaming.py
source:trunk/doc/core/examples/pyuidemo.py
source:trunk/doc/core/examples/sendfd.py
source:trunk/doc/core/examples/pbecho.py
source:trunk/doc/core/examples/echoclient_ssl.py
source:trunk/doc/core/examples/pbsimpleclient.py
source:trunk/doc/core/examples/stdin.py
source:trunk/doc/core/examples/ftpclient.py
source:trunk/doc/core/examples/echoserv_udp.py
source:trunk/doc/core/examples/courier.py
source:trunk/doc/core/examples/cred.py
source:trunk/doc/core/examples/echoclient.py
source:trunk/doc/core/examples/wxacceptance.py
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-5.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-7.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-3.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-4.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-6.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-1.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-2.tac
source:trunk/doc/mail/tutorial/smtpserver/smtpserver-8.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-4.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-5.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-2.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-3.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-6.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-11.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-9.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-10.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-7.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-8.tac
source:trunk/doc/mail/tutorial/smtpclient/smtpclient-1.tac
source:trunk/doc/mail/examples/imap4client.py
source:trunk/doc/mail/examples/emailserver.tac
source:trunk/doc/mail/examples/smtpclient_tls.py
source:trunk/doc/mail/examples/smtpclient_simple.py
source:trunk/doc/web/howto/listings/render_slots_attrs.py
source:trunk/doc/web/howto/listings/xmlrpc-customized.py
source:trunk/doc/web/howto/listings/subviews-1.py
source:trunk/doc/web/howto/listings/client/request.py
source:trunk/doc/web/howto/listings/client/gzipdecoder.py
source:trunk/doc/web/howto/listings/client/sendbody.py
source:trunk/doc/web/howto/listings/client/stringprod.py
source:trunk/doc/web/howto/listings/client/cookies.py
source:trunk/doc/web/howto/listings/client/filesendbody.py
source:trunk/doc/web/howto/listings/client/response.py
source:trunk/doc/web/howto/listings/render_quoting.py
source:trunk/doc/web/howto/listings/render_1.py
source:trunk/doc/web/howto/listings/iteration-1.py
source:trunk/doc/web/howto/listings/transparent_element.py
source:trunk/doc/web/howto/listings/wait_for_it.py
source:trunk/doc/web/howto/listings/render_2.py
source:trunk/doc/web/howto/listings/xmlAndSoapQuote.py
source:trunk/doc/web/howto/listings/element_3.py
source:trunk/doc/web/howto/listings/render_3.py
source:trunk/doc/web/howto/listings/element_2.py
source:trunk/doc/web/howto/listings/quoting_element.py
source:trunk/doc/web/howto/listings/slots_attributes_1.py
source:trunk/doc/web/howto/listings/render_transparent.py
source:trunk/doc/web/howto/listings/element_1.py
source:trunk/doc/web/examples/httpclient.py
source:trunk/doc/web/examples/hello.rpy.py
source:trunk/doc/web/examples/lj.rpy.py
source:trunk/doc/web/examples/fortune.rpy.py
source:trunk/doc/web/examples/web.py
source:trunk/doc/web/examples/xmlrpcclient.py
source:trunk/doc/web/examples/getpage.py
source:trunk/doc/web/examples/rootscript.py
source:trunk/doc/web/examples/dlpage.py
source:trunk/doc/web/examples/logging-proxy.py
source:trunk/doc/web/examples/reverse-proxy.py
source:trunk/doc/web/examples/advogato.py
source:trunk/doc/web/examples/proxy.py
source:trunk/doc/web/examples/xmlrpc.py
source:trunk/doc/web/examples/webguard.py
source:trunk/doc/web/examples/users.rpy.py
source:trunk/doc/web/examples/report.rpy.py
source:trunk/doc/web/examples/soap.py
source:trunk/doc/web/examples/silly-web.py
source:trunk/doc/lore/howto/listings/lore/a_lore_plugin.py
source:trunk/doc/conch/benchmarks/buffering_mixin.py
source:trunk/doc/conch/examples/window.tac
source:trunk/doc/conch/examples/demo.tac
source:trunk/doc/conch/examples/demo_scroll.tac
source:trunk/doc/conch/examples/demo_draw.tac
source:trunk/doc/conch/examples/sshsimpleserver.py
source:trunk/doc/conch/examples/sshsimpleclient.py
source:trunk/doc/conch/examples/telnet_echo.tac
source:trunk/doc/conch/examples/demo_recvline.tac
source:trunk/doc/conch/examples/demo_insults.tac
source:trunk/doc/conch/examples/demo_manhole.tac
source:trunk/doc/names/examples/gethostbyname.py
source:trunk/doc/names/examples/dns-service.py
source:trunk/doc/names/examples/testdns.py
source:trunk/doc/historic/2003/pycon/pb/pb-slides.py
source:trunk/doc/historic/2003/pycon/pb/pb-server1.py
source:trunk/doc/historic/2003/pycon/pb/pb-client1.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-listing1.py
source:trunk/doc/historic/2003/pycon/deferex/deferexex.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-forwarding.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-complex-failure.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-chaining.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-simple-raise.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-bad-adding.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-simple-failure.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-listing2.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-listing0.py
source:trunk/doc/historic/2003/pycon/deferex/deferex-complex-raise.py
source:trunk/doc/historic/2003/pycon/twisted-internet/twisted-internet.py
source:trunk/doc/words/examples/ircLogBot.py
source:trunk/doc/words/examples/oscardemo.py
source:trunk/doc/words/examples/jabber_client.py
source:trunk/doc/words/examples/msn_example.py
source:trunk/doc/words/examples/minchat.py
source:trunk/doc/words/examples/pb_client.py
source:trunk/doc/words/examples/cursesclient.py
source:trunk/doc/words/examples/xmpp_client.py